/*
 * 代号：凤凰
 * http://www.jphenix.org
 * 2014年7月8日
 * V4.0
 */
package com.jphenix.kernel.script;

import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import com.jphenix.clazz.ClassFile;
import com.jphenix.driver.cluster.ClusterFilter;
import com.jphenix.driver.json.Json;
import com.jphenix.driver.nodehandler.FNodeHandler;
import com.jphenix.driver.serialno.FSN;
import com.jphenix.driver.threadpool.ThreadManager;
import com.jphenix.driver.threadpool.ThreadSession;
import com.jphenix.kernel.baseobject.instanceb.ABase;
import com.jphenix.kernel.classloader.ResourceService;
import com.jphenix.kernel.classloader.ResourcesLoader;
import com.jphenix.kernel.objectloader.interfaceclass.IBean;
import com.jphenix.kernel.objectloader.interfaceclass.IBeanRegister;
import com.jphenix.kernel.objectloader.interfaceclass.IBeanRegisterChild;
import com.jphenix.kernel.objectloader.vo.BeanVO;
import com.jphenix.share.lang.SBoolean;
import com.jphenix.share.lang.SDate;
import com.jphenix.share.lang.SInteger;
import com.jphenix.share.lang.SListMap;
import com.jphenix.share.lang.SString;
import com.jphenix.share.lang.SortVector;
import com.jphenix.share.tools.FileCopyTools;
import com.jphenix.share.tools.JarTools;
import com.jphenix.share.util.BaseUtil;
import com.jphenix.share.util.ClassUtil;
import com.jphenix.share.util.DebugUtil;
import com.jphenix.share.util.SFilesUtil;
import com.jphenix.standard.docs.BeanInfo;
import com.jphenix.standard.docs.ClassInfo;
import com.jphenix.standard.docs.Running;
import com.jphenix.standard.exceptions.FixException;
import com.jphenix.standard.exceptions.MsgException;
import com.jphenix.standard.script.IModifyScriptTrigger;
import com.jphenix.standard.script.IScriptBean;
import com.jphenix.standard.script.IScriptInvokeEvent;
import com.jphenix.standard.script.IScriptLoader;
import com.jphenix.standard.script.IScriptNoManager;
import com.jphenix.standard.script.IScriptTrusteeship;
import com.jphenix.standard.serialno.ISN;
import com.jphenix.standard.servlet.IActionBean;
import com.jphenix.standard.servlet.IActionContext;
import com.jphenix.standard.servlet.IFilter;
import com.jphenix.standard.servlet.IServletConst;
import com.jphenix.standard.viewhandler.INodeHandler;
import com.jphenix.standard.viewhandler.IViewHandler;

/**
 * 脚本加载器
 * 
 * 执行步骤：
 * 
 * 1. 先扫描每个编译后的程序，获取类信息 
 * 2. 再扫描源文件（如果存在的话），与编译后的文件信息做比对，不一致的重新编译 
 * 3. 记录最大的程序编号 
 * 
 * 注意：编译后的Java类开头必须是字母，因为类名不能以数字开头，已经做过去掉开头字母的尝试，失败。
 * 
 * 注意：该类不能在jar内部做初始化，调试了很久，总是被锁死
 * 
 * 注意：避免在注册方法中获取传入参数的脚本的脚本信息，因为这个时候脚本还没初始化完毕，获取到的信息是旧的
 * 
 * 注意：脚本支持在内部编写特定方法，可以处理脚本按顺序执行代码时的异常拦截处理doException，
 * 和脚本执行结束后调用的方法doFinally。（具体看接口：IScriptBeanExtra，目前脚本都
 * 集成了BaseParent父类中，已经实现了该接口）
 * 
 * 比如在程序报错时，希望将错误信息以特定格式构建成返回值，顺利返回到调用方，需要在传入参数容器in中设置
 * 一个key值为_RETURN_VALUE_的值，作为脚本在抛异常后，顺利返回的值
 * 
 * 
 * 解决测试环境、开发环境、生产环境脚本名冲突的办法：为脚本名末尾增加项目代码后缀。不通的环境，项目标识是不通的
 * 这样无论在哪个环境上编写代码，整合代码时也不会出现代码冲突。
 * 
 * 2018-06-11 在脚本源码文件夹中，通过配置主键 disabled_file_valid 标记是否允许.disabled文件
 * 以前是通过是否为开发模式控制。但是是否为开发模式控制的东西过多，随后也不知道开发模式开关到底干了些什么。 所以把这个开关提取出来单独控制。
 * 
 * 2018-06-23 增加了一处获取对象是否为空判断，避免抛空指针错误。 
 * 2018-07-09 保存脚本后，并没有调用对应的反注册类方法，只执行了注册类方法 
 * 2018-07-18 修改了日志输出类 
 * 2018-07-23 修改了脚本版本，自动重新编译已有脚本 2018-08-06 完善了调用动作脚本发生异常后，调用异常处理方法
 * 增加了纯java类脚本托管父类功能，即框架调用父类中定义的_executeX方法，可由父类调用其子类脚本中执行入口_execute方法
 * 
 * 2018-08-23 增加了在配置文件中设置禁止指定文件夹的脚本<script_disabled_path><path></path></script_disabled_path> 
 * 2018-09-03 在程序中控制，避免保存内部脚本到外部程序文件夹 
 * 2018-09-05 去掉了是否加载内部脚本的控制（必须加载内部脚本，否则运行不起来）
 * 2018-11-12 因为修改了动作父类跟服务父类，需要重新构造脚本，在此修改了脚本类加载器的版本号 
 * 2018-11-13 简化了父类继承结构，去掉了一个动作类父类和一个服务类父类脚本 
 * 2018-11-19 在脚本中增加了创建日期信息 
 * 2018-11-29 修改了，脚本编译异常时，获取该脚本类实例时抛空指针问题 
 * 2018-12-04 开放出，通过脚本VO保存脚本信息 
 * 2018-12-12 增加了远程调用动作脚本，并返回输出内容对象 
 * 2018-12-13 简化了集群方法调用逻辑 
 * 2018-12-17 增加了获取空脚本信息判断
 * 2018-12-27 允许脚本类作为可注册类（原来只有基础类可以作为可注册类） 
 * 2019-01-11 在执行初始化方法时，去掉了启用线程执行初始化方法，因为有时候线程没执行完，就开始执行注册，导致注册失败
 * 注册主服务脚本（实现了接口IBeanRegister的脚本）如果在热部署后，就失去了之前注册进来的脚本信息。已经解决这问题
 * 修改了注册主类为脚本类时，启动报错问题 又启用了在线程中初始化方法啦，毕竟这么做能避免启动时由于某个脚本初始化方法阻塞整个系统启动。尤其是远程启动时，如果
 * 导致启动阻塞，就无法解决错误了。 2019-01-16 增加了扩展类路径，使指定脚本可以单独加载指定文件夹中所有的jar包 
 * 2019-01-21 去掉了脚本isStatic属性，从来没这么用过 
 * 2019-01-24 修改了遇到的错误 
 * 2019-01-24 发现脚本类加载器不能跟上一级的BeanFactory公用一个资源管理服务（ResourceService），因为原本应该用BeanFactory的类加载器
 * 加载的类，被ScriptClassLoader从BeanFactory的资源管理服务中获取到二进制资源，然后重新构造了一个类，导致两个类不是同一个
 * 类加载器构造的，造成致命的连接错误。 
 * 
 * 2019-01-28 增加了清除脚本运行错误信息方法 
 * 2019-01-31 在调用集群方法时，把调用者类信息传入，方便在日志中查看调用者 
 * 2019-02-14 获取备份根路径方法时，增加了从配置文件中直接获取备份根路径scripts_class_bak_path，而不是直接从脚本段中获取 
 * 2019-03-22 妈的，找到一个死循环错误，已经搞定 
 * 2019-04-09 修改了脚本累加器版本号 
 * 2019-06-13 增加了脚本参数中的默认值，是否需要Base64解码
 * 2019-06-18 更新脚本信息时，调用脚本序号管理类更新脚本编号最大值，避免同步过来的脚本编号比本地大，导致本地创建脚本时覆盖了同步过来的脚本
 * 2019-06-26 修改了序列号生成器的构造方法 
 * 2019-07-02 在保存脚本之后，清除脚本运行错误统计 
 * 2019-07-06 将脚本检查服务放入当前类中启动，而不是用类加载器启动 解决了：注册父脚本如果禁用，则会报错的问题 
 * 2019-07-15 将脚本备份文件夹配置中，增加了可配置根文件夹 
 * 2019-07-16 脚本源码文件夹、脚本编译后的程序文件夹可以自定义 
 * 2019-07-20 改用JDK1.7编译脚本 将脚本信息VO中的绝对路径改为相对路径 
 * 2019-07-21 修改了因为相对路径导致的保存脚本错误问题 
 * 2019-08-02 修改了集群过滤器调用动作脚本时存在的错误 
 * 2019-08-20 增加了模糊搜索指定脚本方法 
 * 2019-08-21 增加了通过脚本标题（名字）模糊搜索符合条件的脚本方法 完美的模糊搜索 
 * 2019-08-24 修改了启动时有的地方用错了日志输出方法 增加了启动结束后提示日志 
 * 2019-08-27 增加了注释 
 * 2019-09-11 修改了拼装脚本方法，禁止拼装调用其它脚本语句时，末尾不加;符号 又简化了代码，去掉了自动判断是否带返回值 
 * 2019-09-17 调整了初始化得顺序，先注册脚本，然后再执行初始化后得方法。如果想在注册之前调用方法，可以执行初始化方法 原本初始化方法跟初始化后方法没什么区别。
 * 2019-09-18 918纪念日，在这里纪念一下。不能忘记的历史，只有面对现实，自己踏踏实实得努力，变强大了，才能实现自己得梦 
 * 2019-09-23 原本保存源码时，源码会做Base64加密，避免代码中包包含与XML格式冲突的关键字。但这样不利于将源码提交到Git做源码比对，目前又改成保存源码方式。
 * 2019-09-24 如果是移植过来的带扩展类路径的脚本，本地环境中没有这个扩展类路径，系统在编译前先做扩展类路径检查，避免在代码中抛编译错误的异常
 * 2019-09-30 增加了判断是否存在指定扩展类相对路径方法 
 * 2019-10-08 修改了内置类加载器版本号 
 * 2019-10-21 增加了自动路由调用（无需加目标群组前缀）目标群组脚本功能 
 * 2019-10-22 增加了调用脚本方法，新的方法中增加了是否需要返回值参数，用于调用远程脚本
 * 2019-10-23 修改了测试时发现的问题 
 * 2019-10-30 增加了保存、删除脚本时触发事件处理 修改了删除脚本时，无法将删除原因写入备份脚本信息中
 * 2019-11-01 修改了设置脚本错误信息的方法（以前是直接设置变量值，现在改用通过方法设置错误信息） 修改了运行时错误记录方法，增加了运行时错误标识
 * 2019-11-14 修改了注册脚本处理顺序，先处理触发器，否则如果按照以前放在后面，有可能在处理之前就推出方法了。 
 * 2019-11-26 修改了保存脚本时，更新次数值没有发生变化的错误 
 * 2019-12-05 修改了调用动作脚本的错误 
 * 2019-12 09 修改了脚本版本号
 * 2019-12-10 新增了集群中调用其它服务器脚本执行上传文件 修改了启动时报空指针错误 
 * 2019-12-12 增加了扩展脚本功能说明的日志
 * 2020-01-20 修改了远程调用时参数序列化与反序列化时发现的错误（无法处理动作类返回数据） 
 * 2020-03-09 为savePathInfo方法增加了返回保存后的文件全路径 
 * 2020-03-10 修改了findScriptByTitle方法，支持脚本id模糊搜索，提高了搜索效率 
 * 2020-03-20 在重新编译脚本的方法中，去掉了远程同群组同步触发，容易产生同步风暴 
 * 2020-03-30 修改了脚本入参勾选Base64解码处理部分（原本代码中用的是编码，不是解码） 
 * 2020-05-14 增加了保存父类，或者别的传统类后，自动重新加载受影响的脚本 
 * 2020-05-22 避免了集群共享脚本源文件夹，检测传统类脚本发生变化后，重新构造，然后回写脚本源文件，导致在集群中，总是重新构造这个传统类脚本 
 * 2020-06-11 修改了rsl抛空指针错误 
 * 2020-07-14 增加了获取远程脚本加载器类实例的方法 
 * 2020-07-20 增加了脚本文件夹中定义该文件夹中脚本的后缀
 * 2020-07-20 增加了通过文件夹路径获取脚本后缀的方法 
 * 2020-08-05 去掉了非XML配置文件解析类，去掉了配置文件接口类，没必要
 * 2020-09-09 脚本支持将传入参数变量设置为类变量（原本是方法内部变量），并且获取脚本类实例代码支持传入类变量值。目前只支持非常驻内存脚本，不支持扩展类脚本
 * 2020-09-18 在项目更新框架包后，如果删除了script_classes文件夹准备重新编译，在全部编译后，会重启一次服务，避免在全部重新编译时出现交叉编译错误。
 * 2020-09-19 脚本主键的后缀只有在当前分类没有后缀的时候，才会继承父分类的后缀
 * 2021-03-17 整理了代码缩进，除去废弃的引用代码，适配注册中心。解决了编译内部脚本错误
 * 2021-04-27 解决了新增类无法实现自动注册的错误
 * 2021-09-11 增加了脚本加载器方法
 * 
 * @author 马宝刚 2014年7月8日
 */
@ClassInfo({ "2021-09-11 22:02", "脚本加载器" })
@BeanInfo({ "scriptloader" })
@Running({ "80", "start" })
public class ScriptLoader extends ABase implements IScriptLoader, IBeanRegister {

  // 如果脚本中的脚本加载器版本号与当前不同，则重新构造脚本类和编译脚本类
  // 如果修改了框架，为脚本中增加了某些东西，需要重新编译脚本，就更新该值
  // 17 增加了属性：extLibPaths
  // 18 2019-01-16 将脚本中的父类都迁移到jar包中
  // 19 2019-01-16 又修改了扩展库支持方法，需要重新构建脚本
  // 20 2019-01-22 去掉了脚本属性关键字 isStatic inFieldList，从来没这么用过
  // 21 2019-01-24 大改动，脚本改成了直接调用，只有加载扩展包路径时才做反射调用
  // 22 2019-01-28 在脚本代码中，增加了抛异常时，记录脚本错误信息功能
  // 23 2019-04-09 在脚本代码中，增加了返回该脚本对应的ScriptVO类实例
  // 27 2019-09-11 修改了拼装脚本方法，禁止拼装调用其它脚本语句时，末尾不加;符号
  // 28 2019-09-11 又简化了代码，去掉了自动判断是否带返回值
  // 29 2019-09-23 原本保存源码时，源码会做Base64加密，避免代码中包包含与XML格式冲突的关键字。
  // 但这样不利于将源码提交到Git做源码比对，目前又改成保存源码方式，保存前会替换掉
  // 源码中与XML冲突的关键字。
  // 这次替换了Java源码中的字符串：<![CDATA[ 替换为：<!^^CDATA[，替换了：]]> 替换为：]^^>
  // 30 2019-10-08 修改了脚本中引用的类的名字 ScriptFileVecotr，改成了 ScriptFileVector
  // 31 2019-10-22 增加了调用脚本方法，新的方法中增加了是否需要返回值参数，用于调用远程脚本需要重新编译脚本
  // 32 2019-12-09 在脚本中增加了doException和doFinally方法，并且增加了传入参数
  // 脚本中存在一个Object[] _SUB_PATAS变量，在脚本过程代码中，可以为这个变量赋值，
  // 以便这个变量值可以传到doException和doFinally方法中
  // 去掉了非XML配置文件解析类，去掉了配置文件接口类，没必要
  // 34 2020-09-09 脚本中增加了属性，是否将传入参数变量作为类变量
  public static final int SCRIPT_LOAD_VER = 35; // 脚本加载器版本号

  // 编译后的程序根路径
  private String binPath = null;

  // 源文件根路径
  private String srcPath = null;

  // 随后还要增加备份路径
  private String bakPath = null; // 备份根路径

  // 脚本编译器
  protected ScriptCompiler sc = null;

  // 是否没有脚本源码，只有编译后的脚本类
  // 在启动时判断并设定
  private boolean classOnly = false;

  // 脚本信息对照容器
  private HashMap<String, ScriptVO> scriptMap = new HashMap<String, ScriptVO>();

  // 脚本每次固定传入参数序列
  private List<ScriptFieldVO> staticFieldList = null;

  // 脚本每次固定导入的类路径序列
  private List<String> importList = null;

  // 重复脚本信息容器
  private Map<String, List<String>> repeatScriptMap = new HashMap<String, List<String>>();

  private ISN sn = null; // 序列号生成器

  // 这个脚本编码管理器是通过注册进来的 （默认为内部编号管理类）
  private IScriptNoManager snm = null; // 脚本编码管理器

  // 集群管理类
  protected ClusterFilter cluster = null;

  // 内部资源加载器
  protected ResourcesLoader rl = null;

  // 脚本文件管理类
  protected ScriptFileManager fm = null;

  // 是否允许保存内部脚本到程序文件夹中
  protected boolean enabledSaveNativeScript = false;

  // 禁止加载的脚本路径
  private List<String> disabledScriptPathList = new ArrayList<String>();

  // 扩展类相对路径序列
  private List<String> extLibPathList = new ArrayList<String>();

  // 脚本类加载器，必须使用这个类加载器加载全部脚本，否则脚本之间无法相互调用
  // 如果想实现每个脚本使用自己独立的类加载器，趁早打消这个念头，具体原因请看
  // ScriptVO中的 注意（190121）
  private ScriptClassLoader scl = null;

  // 脚本类加载器得用自己得资源管理服务，不能使用BeanFactory的。
  // 因为脚本资源管理服务只管理 /WEB-INF/ext_lib 文件夹中所有的jar包
  private ResourceService rs = null; // 包资源管理服务

  private ScriptCheckService scs = null; // 脚本检查服务

  protected RemoteScriptLoader rsl = null; // 远程脚本加载器

  // 脚本修改触发事件处理类序列
  protected List<IModifyScriptTrigger> msTriggerList = new ArrayList<IModifyScriptTrigger>();

  protected Boolean deleteBuildSource = null; // 编译后是否删除java文件
  
  /**
   * 构造函数
   * 
   * @author 马宝刚
   */
  public ScriptLoader() {
    super();
  }

  /**
   * 获取脚本每次固定导入的类路径序列
   * 
   * @return 脚本每次固定导入的类路径序列 2014年8月1日
   * @author 马宝刚
   */
  public List<String> getImportList() {
    return importList;
  }
  
  
  /**
   * 内部启动方法（通常用在架构项目编译内部脚本类）
   * @param isNativeStart 是否为内部启动
   * @throws Exception    异常
   * 2021年9月11日
   * @author MBG
   */
  public void startNative(boolean isNativeStart) throws Exception {

	    log.startLog("\n\nScriptsLoader Native Ver:[" + SCRIPT_LOAD_VER + "]---------------------\n\n");
	    log.startLog("\n\n------------------Start Load The Scripts---------------------\n\n");

	    // 编译后的脚本路径
	    if(binPath==null || binPath.length()<1) {
	    	binPath = infPath("/script_classes");
	    }
	    
	    // 编译脚本后，是否保留编译前的java文件
	    if(deleteBuildSource==null) {
	    	deleteBuildSource = !SBoolean.booleanOf(prop.getParameter("scripts_keep_build_source"));
	    }
	    
	    // 是否允许保存内部脚本
	    enabledSaveNativeScript = SBoolean.booleanOf(prop.getParameter("scripts_enabled_save_native"));

	    // 编译后的脚本根路径是否存在，如果不存在，意味着本次需要全部编译。在编译后会重启该服务
	    // 避免编译时交叉引用时报错，导致项目启动发生异常
	    boolean neeReboot = isNativeStart?false:!(new File(binPath)).exists();

	    // 初始化集群管理类
	    if(!isNativeStart) {
	    	cluster = bean(ClusterFilter.class);
	    }

	    scl = new ScriptClassLoader(null, this, null);
	    scl.addClassPath(binPath);

	    sc = new ScriptCompiler(this); // 构建脚本编译器

	    rl = getBean(ResourcesLoader.class);

	    fm = new ScriptFileManager(this);

	    refreshExtLibPath(); // 初始化扩展类路径信息

	    // 获取需要屏蔽掉的脚本路径（通常用在开发环境中，禁用那些只能在生产环境执行的脚本）
	    List<IViewHandler> disabledPathVhs = prop.getParameterXml("script_disabled_path").getChildNodesByNodeName("path");
	    disabledScriptPathList.clear();
	    String path; // 路径元素
	    for (IViewHandler ele : disabledPathVhs) {
	      path = ele.nt().trim();
	      if (path.length() < 1) {
	        continue;
	      }
	      disabledScriptPathList.add(path);
	    }

	    /*
	     * 初始化每次脚本固定传入参数序列
	     */
	    staticFieldList = new ArrayList<ScriptFieldVO>();
	    // 参数段
	    IViewHandler fieldVh = prop.getParameterXml("scripts").getFirstChildNodeByNodeName("in_paras");
	    List<IViewHandler> fieldVhs = fieldVh.getChildNodesByNodeName("field");
	    ScriptFieldVO sfVO; // 字段信息类
	    for (IViewHandler cfVh : fieldVhs) {
	      sfVO = new ScriptFieldVO();
	      sfVO.name = cfVh.getFirstChildNodeByNodeName("name").nt();
	      sfVO.type = cfVh.getFirstChildNodeByNodeName("type").nt();
	      sfVO.explain = cfVh.getFirstChildNodeByNodeName("explain").nt();
	      sfVO.notNull = SBoolean.valueOf(cfVh.getFirstChildNodeByNodeName("not_null").nt());
	      sfVO.isStaticPara = true;
	      staticFieldList.add(sfVO);
	    }

	    /*
	     * 每次构建脚本时需要固定导入的类路径
	     */
	    importList = new ArrayList<String>();
	    // 参数段
	    fieldVh = prop.getParameterXml("scripts").getFirstChildNodeByNodeName("imports");
	    fieldVhs = fieldVh.getChildNodesByNodeName("import");
	    String importStr; // 导入的类路径元素
	    for (IViewHandler cfVh : fieldVhs) {
	      importStr = cfVh.nt();
	      if (importStr.length() < 1) {
	        continue;
	      }
	      importList.add(importStr);
	    }

	    if(isNativeStart) {
	    	deleteAllBuildFile(); // 删除全部编译好的文件
	    }else {
		    // 如果没有禁用内置脚本，则先加载内置脚本
		    searchNativeSource();
		    // 再加载内置脚本类
		    searchNativeClass();
	    }

	    /*
	     * 初始化加载原则：
	     * 
	     * 1. 当项目中存在脚本源文件时，检测源脚本是否发生变化，如果发生变化，重新编译脚本。
	     * 加载全部源脚本后，检查编译后的脚本，如果发现没有对应的源文件的脚本类，直接删除，并不加载。
	     * 
	     * 2. 当项目中不存在脚本源文件时，直接加载编译后的脚本类。
	     */
	    int searchCount = searchSource(); // 先搜索脚本源文件
	    // 删除没有关联脚本源码的类
	    // 通常在本地测试环境中，生成编译后的脚本类后，因为后期不需要
	    // 将脚本源码删除，但是编译后的类还存在，导致脚本重复，运行出错
	    classOnly = searchCount < 1;
	   
	    checkLoadClass(); // 搜索编译后的脚本程序

	    initScripts(isNativeStart); // 初始化脚本类

	    log.startLog("===============ParseMaxIDs Count:[" + scriptMap.size() + "] SNM HashCode:["
	        + getScriptNoManager().hashCode() + "]");

	    // 放在末尾，都初始化完毕后执行
	    getScriptNoManager().setMaxIds(scriptMap);

	    // 启动脚本检查服务
	    scs = new ScriptCheckService(this);
	    _beanFactory.setObject(ScriptCheckService.class, scs);
	    scs.start();

	    // 构建启动远程脚本加载器
	    rsl = new RemoteScriptLoader(this, cluster);

	    if (neeReboot) {
	      info("*** ≥Y≤ All Script Build Completed,Begin ReBoot Serve For Reload Again .....");
	      // 只有手动重启返回值为1
	      if (boo(p("system_no_halt"))) {
	        System.err.print("******System Begin Exit******");
	        System.exit(1);
	      } else {
	        System.err.print("******System Begin Halt******");
	        Runtime.getRuntime().halt(1);
	      }
	    } else {
	      log.startLog("\n--------------------------------------------------------------------\n"
	          + "*** ≥A≤ The Kernel ScriptEngine Start Completed ***\n"
	          + "--------------------------------------------------------------------\n\n");
	    } 
  }

  /**
   * 启动脚本加载器
   * 
   * @throws Exception 异常 2014年7月8日
   * @author 马宝刚
   */
  public void start() throws Exception {
	  startNative(false);
  }

  /**
   * 检测存在扩展类路径的脚本，实际环境中是否存在该脚本要求的全部扩展类路径
   * 
   * @param sVO 待检测脚本信息类
   * @return true 存在问题 false正常 2019年9月24日
   * @author MBG
   */
  private boolean checkScriptExtLib(ScriptVO sVO) {
    if (sVO.extLibPaths == null) {
      return false;
    }
    boolean noFind = false; // 是否脚本中声明了需要使用某个扩展类，但实际环境中没有这个扩展类
    for (int i = 0; i < sVO.extLibPaths.length; i++) {
      if (!extLibPathList.contains(sVO.extLibPaths[i])) {
        noFind = true;
        break;
      }
    }
    if (noFind) {
      refreshExtLibPath(); // 重置常驻内存的，已存在的扩展库信息
      // 然后再查一遍
      noFind = false; // 是否脚本中声明了需要使用某个扩展类，但实际环境中没有这个扩展类
      for (int i = 0; i < sVO.extLibPaths.length; i++) {
        if (!extLibPathList.contains(sVO.extLibPaths[i])) {
          noFind = true;
          break;
        }
      }
      if (noFind) {
        // 构建错误信息
        StringBuffer sbf = new StringBuffer();
        sbf.append("\n\n***ExtLib Error*************************************\nThe Script:[").append(sVO.id)
            .append("] Has ExtLib:[");
        for (int i = 0; i < sVO.extLibPaths.length; i++) {
          if (i > 0) {
            sbf.append(",");
          }
          sbf.append(sVO.extLibPaths[i]);
        }
        sbf.append("] But The System Not Found Some One\n***ExtLib Error*************************************\n\n");
        sVO.addErrorMsg(sbf.toString());
        return true;
      }
    }
    return false;
  }

  /**
   * 判断是否存在指定扩展类相对路径（多个扩展类相对路径用半角逗号分割）
   * 
   * @return true存在全部扩展类相对路径， false其中或全部相对路径不存在
   * 
   *         扩展类相对路径，相对于 /WEB-INF/ext_lib 文件夹 类路径开头不包含/
   * 
   *         2019年9月30日
   * @author MBG
   */
  @Override
  public boolean hasExtLib(String libSubPath) {
    // 按逗号分割
    List<String> paths = BaseUtil.splitToList(libSubPath, ",");
    boolean noExist = false;
    for (String path : paths) {
      if (!extLibPathList.contains(path)) {
        noExist = true;
        break;
      }
    }
    return !noExist;
  }

  /**
   * 初始化扩展类路径信息序列 2019年9月24日
   * 
   * @author MBG
   */
  public void refreshExtLibPath() {
    extLibPathList.clear();
    initExtLibPath(infPath("/ext_lib"), "");
  }

  /**
   * 初始化指定扩展类文件夹 （递归函数）
   * 
   * @param basePath 文件夹根路径
   * @param subPath  相对路径中的根路径（即：ext_lib 中的一级子路径） 2019年9月24日
   * @author MBG
   */
  private void initExtLibPath(String basePath, String subPath) {
    // 搜索到的文件路径
    List<String> pathList = new ArrayList<String>();
    try {
      // 执行搜索项目库文件夹
      SFilesUtil.getFileList(pathList, basePath, null, null, false, true);
    } catch (Exception e) {
      e.printStackTrace();
      return;
    }
    File checkFile; // 检测文件对象
    for (String path : pathList) {
      checkFile = new File(basePath + "/" + path);
      if (!checkFile.exists() || !checkFile.isDirectory()) {
        continue;
      }
      if (!extLibPathList.contains(subPath + path)) {
        extLibPathList.add(subPath + path);
      }
      // 递归获取子路径
      initExtLibPath(basePath + "/" + path, subPath + path + "/");
    }
  }

  /**
   * 获取指定类信息（递归函数，如果当前类中没有，就去父类中找）
   * @param svo      脚本信息类
   * @param infoType 0 initMethodName  1 afterStartMethodName  2 destoryMethodName  3 registerID
   * @return         指定类信息（返回值不会为null)
   */
  private String getBeanClassInfo(ScriptVO svo,int infoType){
    if(svo==null){
      return "";
    }
    String reVal = null; //返回值
    switch(infoType){
      case 0:
        reVal = svo.initMethodName;
        break;
      case 1:
        reVal = svo.afterStartMethodName;
        break;
      case 2:
        reVal = svo.destoryMethodName;
        break;
      case 3:
        reVal = svo.registerID;
        break;
    }
    if(reVal==null || reVal.length()<1){
      return str(getBeanClassInfo(svo.parentBeanID,infoType));
    }
    return reVal==null?"":reVal;
  }

  /**
   * 获取指定类信息（递归函数，如果当前类中没有，就去父类中找）
   * @param beanId   类主键
   * @param infoType 0 initMethodName  1 afterStartMethodName  2 destoryMethodName  3 registerID
   * @return         指定类信息（返回值不会为null)
   */
  private String getBeanClassInfo(String beanId,int infoType){
    if(beanId==null || beanId.length()<1){
      return "";
    }
    String reVal = null; //返回值
    if(hasScript(beanId)) {
      // 父类也是脚本主键
      ScriptVO svo = getScriptInfo(beanId, false);
      if (svo==null) {
        return "";
      }
      switch(infoType){
        case 0:
          reVal = svo.initMethodName;
          break;
        case 1:
          reVal = svo.afterStartMethodName;
          break;
        case 2:
          reVal = svo.destoryMethodName;
          break;
        case 3:
          reVal = svo.registerID;
          break;
      }
      if(reVal==null || reVal.length()<1){
        return str(getBeanClassInfo(svo.parentBeanID,infoType));
      }
    }else if(_beanFactory.beanExists(beanId)){
      // 类工厂管理的类信息容器
      BeanVO bvo = null;
      try{
        bvo = _beanFactory.getBeanVO(beanId,this);
      }catch(Exception e){}
      if (bvo==null) {
        return "";
      }
      switch(infoType){
        case 0:
          reVal = bvo.initMethodName;
          break;
        case 1:
          reVal = bvo.afterStartMethodName;
          break;
        case 2:
          reVal = bvo.destoryMethodName;
          break;
        case 3:
          reVal = bvo.beanRegister;
          break;
      }
      if(reVal==null || reVal.length()<1){
        return str(getBeanClassInfo(bvo.parentID,infoType));
      }
    }
    return reVal==null?"":reVal;
  }

  /**
   * 初始化脚本类
   * 
   * 集中生成需要编译的java类，然后再逐个编译 否则编译某个脚本时会因为需要交叉编译，导致 编译失败
   * 
   * @param nativeStart 是否为内部启动（内部启动时，并不会注册脚本和执行初始化后的方法，因为只用来编译脚本）
   * 
   * 2014年7月23日
   * 
   * @author 马宝刚
   */
  protected void initScripts(boolean nativeStart) {
    // 获取脚本主键序列
    List<String> scriptIdList = BaseUtil.getMapKeyList(scriptMap);
    ScriptVO sVO; // 脚本信息类
    SortVector<ScriptVO> inSv = new SortVector<ScriptVO>(); // 传统类排序容器
    SortVector<ScriptVO> sv   = new SortVector<ScriptVO>(); // 脚本排序容器
    for (String scriptId : scriptIdList) {
      sVO = scriptMap.get(scriptId);
      if (sVO.disabled) {
        continue;
      }
      if (sVO.independentClass) {
        inSv.add(sVO, sVO.singletonLevel);
      } else {
        sv.add(sVO, sVO.singletonLevel);
      }
    }
    // 将这部分放到启动后(start方法末尾)执行。因为如果本地服务也配置了访问localhost
    // 设置最大脚本编号，在服务还没初始化完毕时调用，就会出现锁死
    // if(snm!=null) {
    // //与服务器端同步最大脚本编号
    // snm.setMaxId(maxScriptIdMap);
    // }
    // 需要重新编译的脚本信息容器序列
    List<ScriptVO> reBuildScriptList = new ArrayList<ScriptVO>(); // 需要编译的普通脚本
    // 先构造传统类，因为类主键在编译后才能知道
    // 脚本如果引用了传统类主键，才能找到对应的传统类
    inSv.desc();
    while (inSv.hasNext()) {
      sVO = inSv.nextValue();
      if (!sVO.disabled && !sVO.nativeClass && sVO.sourceFilePath != null
          && (sVO.sourceFileTime > 0 || sVO.sourceFileStatus != 0)
          && (sVO.classFileVer == null || !sVO.classFileVer.equals(sVO.sourceFileVer))
          || (sVO.classFileVer != null && sVO.loaderChanged())) {
        // 生成需要编译的类
        sc.buildSource(sVO);
        // 放入待编译序列
        reBuildScriptList.add(sVO);
      }
    }
    // 执行编译 先编译传统类
    for (ScriptVO ele : reBuildScriptList) {
      info("(^_^)Begin Compile Independent Script:[" + ele.id + "]");
      // 执行编译
      sc.buildClass(ele);
    }
    // 初始化传统类
    inSv.desc();
    while (inSv.hasNext()) {
      sVO = inSv.nextValue();
      info("(^_^)Begin Load Independent Class Script:[" + sVO.id + "]");
      initIndependentClass(sVO, null);
    }
    info("\n\n( ^3^ )╱~~  ScriptLoader Load Independent Class Completed!\n\n");
    // 再编译普通脚本类
    reBuildScriptList = new ArrayList<ScriptVO>();
    sv.desc();
    while (sv.hasNext()) {
      sVO = sv.nextValue();
      if (!sVO.disabled && !sVO.nativeClass && sVO.sourceFilePath != null
          && (sVO.sourceFileTime > 0 || sVO.sourceFileStatus != 0)
          && (sVO.classFileVer == null || !sVO.classFileVer.equals(sVO.sourceFileVer))
          || (sVO.classFileVer != null && sVO.loaderChanged())) {
        // info("(^_^)Begin Build Script:["+sVO.id+"] Source"); //没用，这步不会出错
        // 生成需要编译的类
        sc.buildSource(sVO);
        // 放入待编译序列
        reBuildScriptList.add(sVO);
      }
    }
    // 再编译普通类
    for (ScriptVO ele : reBuildScriptList) {
      /*
       * //传统类也可以加载扩展库了，但实际上扩展库是加载到全局类加载器中的，并没有区分不通的类加载器加载不同的扩展库 //具体原因看类顶部的日志
       * //检测扩展类路径是否存在 if(ele.haveExtLibPaths) { if(checkScriptExtLib(ele)) {
       * //不在这里输出日志，因为后面会输出这个错误日志 //log.startLog(ele.errorMsg); continue; } }
       */
      info("(^_^)Begin Compile Script:[" + ele.id + "] ["+ele.title+"]");
      // 执行编译
      sc.buildClass(ele);
    }
    info("\n\n( ^3^ )╱~~  ScriptLoader Compile Script Completed!\n\n");

    // 需要在初始化后调用方法的脚本类信息序列
    List<String> afterStartMethodNameList = new ArrayList<String>();
    List<ScriptVO> afterStarVOtList = new ArrayList<ScriptVO>();
    // 需要注册的脚本类序列
    List<String> registerIdList = new ArrayList<String>();
    List<ScriptVO> registerVOList = new ArrayList<ScriptVO>();

    String initMethodName; // 初始化方法
    String afterStartMethodName; // 初始化后执行的方法
    String registerId; // 注册类主键
    Object scriptObj; // 常驻内存的脚本类实例
    sv.desc();
    while (sv.hasNext()) {
      sVO = sv.nextValue();
      if (sVO.disabled) {
        info("The Script:[" + sVO.id + "] Has Set Disabled");
        // 删除目标编译类
        // 将相对路径转换为全路径
        String scriptClassPath = getClassBasePath() + sVO.classFilePath;
        // 删除该类对应的线程类 去掉尾部的 .class 扩展名，这样删除包括线程类文件和java源文件
        ScriptUtil.deleteScript(scriptClassPath.substring(0, scriptClassPath.length() - 6), false);
        continue;
      }
      if (sVO.hasError()) {
        warning("The Script Object:[" + sVO.id + "] Not Init Because;[" + sVO.errorMsg + "]", null);
        continue;
      }
      if (sVO.id == null || sVO.id.length() < 1) {
        warning("The Script Path:[" + getSourceBasePath() + sVO.sourceFilePath + "] Has No FileName(ScriptID)");
        continue;
      }
      if (sVO.singletonLevel > 0) {
        try {
          // 对常驻内存的类做初始化
          scriptObj      = getScript(sVO);
          initMethodName = getBeanClassInfo(sVO,0);
          if(initMethodName.length()>0) {
            try {
              // 获取初始化方法
              Method initMethod = scriptObj.getClass().getMethod(initMethodName);
              // 执行初始化方法
              initMethod.invoke(scriptObj);
            } catch (Exception e) {
              e.printStackTrace();
              sVO.addErrorMsg("The Script ID:[" + sVO.id + "] Execute The Init Method:[" + initMethodName
                  + "] Exception:" + DebugUtil.getExceptionInfo(e, "\n"));
              error("The Script ID:[" + sVO.id + "] Execute The Init Method Exception", e);
              continue;
            }
          }
        } catch (Exception e) {
          e.printStackTrace();
        }
      }
      afterStartMethodName = getBeanClassInfo(sVO,1);
      if (afterStartMethodName.length() > 0) {
        afterStartMethodNameList.add(afterStartMethodName);
        afterStarVOtList.add(sVO);
      }
      registerId = getBeanClassInfo(sVO,3);
      if(registerId.length()>0) {
        registerIdList.add(registerId);
        registerVOList.add(sVO);
      }
    }
    ScriptVO eVO; // 脚本元素
    Object scriptBean = null; // 脚本类实例

    /*
     * 执行注册
     */
    if(!nativeStart) {
        for (int i = 0; i < registerIdList.size(); i++) {
            registerId = registerIdList.get(i);
            eVO = registerVOList.get(i);
            ScriptVO masterVO; // 注册主服务脚本信息容器
            try {
              scriptBean = getScript(eVO);
              // 因为脚本可以做热部署（重新构造初始化），如果服务主脚本（其它脚本都注册到该脚本）
              // 这个主脚本做了热部署，之前注册进来的脚本就都丢失了，无法再用。所以需要将注册
              // 进来的脚本主键保存到主脚本信息容器的这个序列中，在热部署之后，重新将这些脚本
              // 在注册进来
              if (hasScript(registerId)) {
                masterVO = scriptMap.get(registerId);
                if (masterVO.disabled) {
                  continue;
                }
                if (masterVO.registerBeanIDs == null) {
                  masterVO.registerBeanIDs = new ArrayList<String>();
                }
                if (!masterVO.registerBeanIDs.contains(eVO.id)) {
                  masterVO.registerBeanIDs.add(eVO.id);
                }
                ((IBeanRegister) getScript(masterVO)).regist(scriptBean);
              } else if (getBeanFactory().beanExists(registerId)) {
                ((IBeanRegister) getBeanFactory().getObject(registerId, this)).regist(scriptBean);
              } else {
                eVO.addErrorMsg("The Script:[" + eVO.id + "] Execute Regist ID:[" + registerId + "] Not Find The Bean ID");
              }
            } catch (IncompatibleClassChangeError error) {
              error.printStackTrace();
              eVO.addErrorMsg(
                  "The Script:[" + eVO.id + "] Execute Regist ID:[" + registerId + "] IncompatibleClassChangeError:" + error);
              error("The Script:[" + eVO.id + "] Execute Regist IncompatibleClassChangeError:[" + error + "]", null);
            } catch (Exception e) {
              e.printStackTrace();
              eVO.addErrorMsg("The Script:[" + eVO.id + "] Execute Regist ID:[" + registerId + "] Exception:"
                  + DebugUtil.getExceptionInfo(e, "\n"));
              error("The Script:[" + eVO.id + "] Execute Regist Exception", e);
            }
          }
          log.startLog("\n\n( ^3^ )╱~~  ScriptLoader Regist Script Completed!\n\n");
          
          
          /*
           * 执行在初始化后需要执行指定方法 注意：不能用启动线程做初始化，因为这不是最后一步，随后还要做注册，如果执行初始化线程还没结束，接着就做注册
           * 就回出现注册失败，导致有些服务无法正常工作。
           * 
           * 当初决定采用线程做初始化，是因为避免某个脚本执行初始化时阻塞，导致整个网站无法启动
           * 
           * 现在来看，要想避免因为个别脚本可能会导致阻塞（通常不会有这种情况，及时出现这种情况，也是必须要处理那个
           * 有问题的脚本）会付出很大代价，所以决定暂时不考虑这种情况。万一遇到这种情况，就解决那个发生阻塞的脚本
           */
          // Object time; //初始化耗费时间（已废弃，请看使用部分的注释）
          for (int i = 0; i < afterStartMethodNameList.size(); i++) {
            afterStartMethodName = afterStartMethodNameList.get(i);
            eVO = afterStarVOtList.get(i);

            // 由于有些脚本的初始化后执行方法非常耗时，如果不采用线程方式执行
            // 会阻塞进程导致系统迟迟无法进入正常工作状态，甚至导致系统无法启动完毕
            // 注意：如果某各类在初始化时需要调用另外的类，那么那个类就不能用初始化后
            // 执行的方法了，而是初始化方法，但是在初始化方法中要绝对避免阻塞，通常避免
            // 在初始化时调用另外的需要初始化的程序，也避免使用初始化方法，提倡使用初始化后
            //
            // 后来发现的那个问题（怀疑是因为没初始化完导致的问题）实际上不是因为初始化造成的
            // 启线程初始化啊，你受委屈了啊~~~~
            // 决定还是通过起线程方式执行每个脚本的初始化，因为这种方式至少不会导致启动阻塞,
            // 在远程重启后导致系统无法启动，也就无法远程解决导致阻塞的问题。

            // 启线程初始化理论上会遇到这种问题：脚本A在初始化时调用了脚本B，但是脚本B还没初始化完。
            // 不过这种情况可以避免的，也不影响整个系统启动。如果报错了，想办法规避这种情况

            /*
             * 以下是不启线程执行初始化的方法
             * 
             * 
             * info("--- (；°○° ) Begin After Start Execute ["+eVO.id+"] Method:["
             * +afterStartMethodName+"]"); time = log.runBefore(); try { //获取脚本类实例
             * scriptBean = getScript(eVO); if(scriptBean!=null) { //获取初始化方法 Method asMethod
             * = scriptBean.getClass().getMethod(afterStartMethodName,new Class[] {});
             * //执行初始化方法 asMethod.invoke(scriptBean,new Object[] {}); }
             * log.writeRuntime(time,"--- ∩△∩  After Start Execute {"+eVO.id+"] Method:["
             * +afterStartMethodName+"]"); }catch(Exception e) { e.printStackTrace();
             * eVO.addErrorMsg("The Script:["+eVO.id+"] Execute AfterStart Method:["
             * +afterStartMethodName+"] Exception:"+DebugUtil.getExceptionInfo(e,"\n"));
             * error("The Script:["+eVO.id+"] Execute AfterStart Method Exception",e); }
             */

            // 执行的方法
            (new Thread() {
              private ScriptVO eVO = null; // 脚本信息类
              private String afterStartMethodName = null; // 初始化后需要执行的方法名

              /**
               * 设置在线程中执行方法需要的参数
               * 
               * @param eVO                  脚本信息类
               * @param afterStartMethodName 初始化后需要执行的方法名
               * @return 当前线程类实例 2017年11月12日
               * @author MBG
               */
              public Thread setInfo(ScriptVO eVO, String afterStartMethodName) {
                this.eVO = eVO;
                this.afterStartMethodName = afterStartMethodName;
                return this;
              }

              /**
               *
               * 覆盖方法
               */
              @Override
              public void run() {
                info("--- (；°○° ) Begin After Start Execute [" + eVO.id + "] Method:[" + afterStartMethodName + "]");
                Object time = log.runBefore();
                try {
                  // 获取脚本类实例
                  Object scriptBean = getScript(eVO);
                  if (scriptBean != null) {
                    // 获取初始化方法
                    Method asMethod = scriptBean.getClass().getMethod(afterStartMethodName);
                    // 执行初始化方法
                    asMethod.invoke(scriptBean);
                  }
                  log.writeRuntime(time,
                      "--- ∩△∩  After Start Execute {" + eVO.id + "] Method:[" + afterStartMethodName + "]");
                } catch (Exception e) {
                  e.printStackTrace();
                  eVO.addErrorMsg("The Script:[" + eVO.id + "] Execute AfterStart Method:[" + afterStartMethodName
                      + "] Exception:" + DebugUtil.getExceptionInfo(e, "\n"));
                  error("The Script:[" + eVO.id + "] Execute AfterStart Method Exception", e);
                }
              }
            }).setInfo(eVO, afterStartMethodName).start();
          }
    }

    // 获取重复脚本信息
    List<String> repeatInfoList = getRepeatScriptInfo();
    if (repeatInfoList.size() > 0) {
      StringBuffer sbf = new StringBuffer();
      sbf.append("\n<><><><><><><><><><>The Script Has Repeat:<><><><><><><><><><>\n");
      for (String ele : repeatInfoList) {
        sbf.append("<><><><><>").append(ele).append("\n");
      }
      sbf.append("<><><><><><><><><><><><><><><><><><><><>\n");
      log.startLog(sbf.toString());
    }
  }

  /**
   * 获取包含脚本类实例的脚本信息类
   * 
   * @param scriptID 脚本主键
   * @return 包含脚本类实例的脚本信息类
   * @throws Exception 异常 2014年8月26日
   * @author 马宝刚
   */
  @Override
  public ScriptVO getScriptVOObject(String scriptID) throws Exception {
    // 获取指定的脚本信息类
    ScriptVO sVO = scriptMap.get(scriptID);
    Object reObj = getScript(sVO); // 获取脚本类实例
    if (sVO == null) {
      warning("getScriptVOObject Not Find The Script:[" + scriptID + "]", null);
      return null;
    }
    // 出于保护ScriptVO中的信息被篡改
    sVO = sVO.clone();
    sVO.scriptObj = reObj;
    return sVO;
  }

  /**
   * 获取指定的脚本信息
   * 
   * @param scriptID 脚本信息主键
   * @return 脚本信息类 2014年7月23日
   * @author 马宝刚
   */
  @Override
  public ScriptVO getScriptInfo(String scriptID) {
    return getScriptInfo(scriptID, true);
  }

  /**
   * 获取指定的脚本信息
   * 
   * @param scriptID 脚本信息主键
   * @param clone    是否输出克隆对象
   * @return 脚本信息类 2014年8月15日
   * @author 马宝刚
   */
  public ScriptVO getScriptInfo(String scriptID, boolean clone) {
    if (scriptID == null || scriptID.length() < 1) {
      return null;
    }
    // 获取指定的脚本信息类
    ScriptVO sVO = scriptMap.get(scriptID);
    if (sVO != null) {
      if (clone) {
        return sVO.clone();
      }
      return sVO;
    }
    if (_beanFactory.beanExists(scriptID)) {
      try {
        // 获取传统类信息
        BeanVO bVO = _beanFactory.getBeanVO(scriptID, this);
        if (bVO != null && bVO.classPath != null) {
          sVO = scriptMap.get(bVO.classPath.substring(bVO.classPath.lastIndexOf(".") + 1));
          if (sVO != null) {
            if (clone) {
              return sVO.clone();
            }
            return sVO;
          }
        }
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
    return null;
  }

  /**
   * 获取脚本主键，名称对照容器序列
   * 
   * @return 脚本主键，名称对照容器序列 2014年8月21日
   * @author 马宝刚
   */
  @Override
  public SListMap<String> getScriptInfoList() {
    // 构建返回值
    SListMap<String> res = new SListMap<String>();
    // 排序序列
    SortVector<String> sv = new SortVector<String>();
    // 获取脚本主键序列
    List<String> keyList = BaseUtil.getMapKeyList(scriptMap);
    for (String id : keyList) {
      sv.add(id, SInteger.valueOf(id));
    }
    sv.asc();
    ScriptVO sVO; // 脚本元素
    while (sv.hasNext()) {
      sVO = scriptMap.get(sv.nextValue());
      res.put(sVO.id, sVO.title);
    }
    return res;
  }

  /**
   * 获取脚本类实例
   * 
   * @param scriptID 脚本类实例
   * @return 脚本类实例
   * @throws Exception 异常 2014年7月23日
   * @author 马宝刚
   */
  @Override
  public IScriptBean getScript(String scriptID) throws Exception {
    // 获取指定的脚本类
    ScriptVO sVO = scriptMap.get(scriptID);
    if (sVO == null) {
      throw new MsgException(this, "The Scriopt ID:[" + scriptID + "] Has Not Found");
    }
    return getScript(sVO);
  }

  /**
   * 除去脚本末尾后缀（如果脚本主键全是字母，则返回全部脚本名）（目前没用上）
   * 
   * @param scriptID 脚本主键
   * @return 除去后缀的脚本主键值 2019年8月20日
   * @author MBG
   */
  protected static String cutSuffix(String scriptID) {
    byte[] bytes = scriptID.getBytes(); // 获取待处理字节
    int lastPoint = bytes.length;
    for (int i = bytes.length - 1; i > -1; i--) {
      if ((int) bytes[i] > 47 && (int) bytes[i] < 58) {
        // 是数字
        break;
      }
      lastPoint--;
    }
    if (lastPoint == 0) {
      return scriptID;
    }
    return new String(bytes, 0, lastPoint);
  }

  /**
   * 通过脚本标题（名字）关键字搜索符合条件的脚本序列容器
   * 
   * @param titleKey 脚本标题（名字）关键字
   * @return 符合条件的脚本序列容器 2019年8月21日
   * @author MBG
   */
  @Override
  public SListMap<String> findScriptByTitle(String titleKey) {
    // 构建返回值
    SListMap<String> reMap = new SListMap<String>();
    if (titleKey == null || titleKey.length() < 1 || scriptMap.size() < 1) {
      return reMap;
    }
    // 分割为多个关键字
    String[] keys = BaseUtil.split(titleKey, "*", " ");

    // 处理后的关键字序列
    List<String> keyList = new ArrayList<String>();
    for (int i = 0; i < keys.length; i++) {
      keys[i] = keys[i].trim();
      if (keys[i].length() < 1 || keyList.contains(keys[i])) {
        continue;
      }
      keyList.add(keys[i].toLowerCase());
    }
    keys = new String[keyList.size()];
    keyList.toArray(keys);

    Iterator<ScriptVO> svoIterator = scriptMap.values().iterator();
    ScriptVO sVO; // 脚本信息容器
    while (svoIterator.hasNext()) {
      sVO = svoIterator.next();
      if (sVO == null) {
        continue;
      }
      if (sVO.id == null && sVO.title == null) {
        continue;
      }
      for (int i = 0; i < keys.length; i++) {
        if (sVO.id != null) {
          if (sVO.id.toLowerCase().indexOf(keys[i]) > -1) {
            reMap.put(sVO.id, sVO.title);
            continue;
          }
        }
        if (sVO.title != null) {
          if (sVO.title.toLowerCase().indexOf(keys[i]) > -1) {
            reMap.put(sVO.id, sVO.title);
            continue;
          }
        }
      }
    }
    return reMap;
  }

  /**
   * 模糊搜索符合条件的脚本主键（支持脚本主键字符串中间包含*符号）
   * 
   * @param scriptID 脚本主键关键字
   * @return 符合条件的脚本主键脚本名对照容器 2019年8月20日
   * @author MBG
   */
  @Override
  public SListMap<String> findScript(String scriptID) {
    // 构建返回值
    SListMap<String> reMap = new SListMap<String>();
    if (scriptID == null || scriptID.length() < 1) {
      return reMap;
    }
    scriptID = scriptID.toUpperCase();
    ScriptVO sVO; // 脚本信息容器
    // 先放入完全匹配的
    if (scriptMap.containsKey(scriptID)) {
      sVO = scriptMap.get(scriptID);
      reMap.put(scriptID, sVO.title);
    }
    // 没有通配符
    String key; // 脚本主键元素
    // 已遍历出的脚本主键序列
    List<String> scriptKeyList = new ArrayList<String>();
    // 获取脚本主键迭代
    Iterator<String> keysIterator = scriptMap.keySet().iterator();
    if (keysIterator == null) {
      return reMap;
    }
    while (keysIterator.hasNext()) {
      key = keysIterator.next();
      if (key == null || key.length() < 1) {
        continue;
      }
      scriptKeyList.add(key.toUpperCase());
    }
    // 处理后剩余的脚本主键序列
    List<String> lastKeys = new ArrayList<String>();
    // 分割为多个关键字
    String[] keys = BaseUtil.split(scriptID, "*", " ");
    for (int i = 0; i < keys.length; i++) {
      keys[i] = keys[i].trim();
      if (keys[i].length() < 1) {
        continue;
      }
      for (String ele : scriptKeyList) {
        if (ele.indexOf(keys[i]) > -1) {
          lastKeys.add(ele);
        }
      }
      scriptKeyList.clear();
      scriptKeyList.addAll(lastKeys);
      lastKeys.clear();
    }
    for (String ele : scriptKeyList) {
      sVO = scriptMap.get(ele);
      if (sVO == null) {
        continue;
      }
      reMap.put(ele, sVO.title);
    }
    return reMap;
  }

  /**
   * 不抛异常方式获取指定脚本类实例（务必确认好是否存在该脚本）
   * 
   * @param scriptID 脚本主键
   * @return 脚本类实例 2016年3月17日
   * @author 马宝刚
   */
  @Override
  public IScriptBean getScriptNoException(String scriptID) {
    // 获取指定的脚本类
    ScriptVO sVO = scriptMap.get(scriptID);
    if (sVO == null) {
      warning("The Scriopt ID:[" + scriptID + "] Has Not Found");
    }
    try {
      return getScript(sVO);
    } catch (Exception e) {
      warning("Get The Script ID:[" + scriptID + "] Exception:" + e);
      e.printStackTrace();
    }
    return null;
  }

  /**
   * 不抛异常方式获取指定脚本类实例（务必确认好是否存在该脚本）
   * 
   * 注意：目前这个脚本不能是常驻内存脚本，不能是扩展类脚本
   * 
   * @param scriptID 脚本主键
   * @param paraMap  在返回类实例之前，将参数容器放入线程会话中，会话主键名为当前脚本主键
   * @return 脚本类实例 2016年3月17日
   * @author 马宝刚
   */
  @SuppressWarnings("rawtypes")
  @Override
  public IScriptBean getScriptNoException(String scriptID, Map paraMap) {
    // 获取指定的脚本类
    ScriptVO sVO = scriptMap.get(scriptID);
    if (sVO == null) {
      warning("The Scriopt ID:[" + scriptID + "] Has Not Found");
    }
    try {
      // 获取指定脚本类实例，并且将参数容器中的值设置到类实例的类变量中
      return getScript(sVO, false, paraMap);
    } catch (Exception e) {
      warning("Get The Script ID:[" + scriptID + "] Exception:" + e);
      e.printStackTrace();
    }
    return null;
  }

  /**
   * 不抛异常方式（务必确认好是否存在该脚本）
   * 
   * @param scriptID 脚本主键
   * @return 脚本类实例 2016年3月17日
   * @author 马宝刚
   */
  @Override
  public IScriptBean script(String scriptID) {
    // 获取指定的脚本类
    ScriptVO sVO = scriptMap.get(scriptID);
    if (sVO == null) {
      warning("The Scriopt ID:[" + scriptID + "] Has Not Found");
    }
    try {
      return getScript(sVO);
    } catch (Exception e) {
      warning("Get The Script ID:[" + scriptID + "] Exception:" + e);
      e.printStackTrace();
    }
    return null;
  }

  /**
   * 获取脚本类实例
   * 
   * @param sVO 脚本信息类
   * @return 脚本类实例
   * @throws Exception 异常 2014年8月22日
   * @author 马宝刚
   */
  public IScriptBean getScript(ScriptVO sVO) throws Exception {
    return getScript(sVO, false, null);
  }

  /**
   * 获取脚本类实例（内部使用） 在脚本判断发生变化后，会先终止该脚本，重新做初始化
   * 终止脚本前，先会执行取消注册动作，在执行这个动作之前，会获取这个脚本的类实例
   * 在获取脚本的类实例之前，会判断这个脚本是否发生变化，结果完犊子了，发现这个脚本发生变化 然后死循环
   * 
   * @param sVO           脚本信息类
   * @param noCheckChange 是否不做脚本检测
   * @param paraMap       在构建好脚本类实例后，需要设置的类变量参数值容器（注意：该脚本必须不是常驻内存的）
   * @return 脚本类实例
   * @throws Exception 异常 2014年8月22日
   * @author 马宝刚
   */
  @SuppressWarnings("rawtypes")
  private IScriptBean getScript(ScriptVO sVO, boolean noCheckChange, Map paraMap) throws Exception {
    if (sVO == null) {
      return null;
    }
    if (!noCheckChange) {
      checkScriptChange(sVO); // 检查脚本内容是否发生变化，如果发生变化则更新脚本
    }
    // 不能在这里设置，比如某个服务，在服务中加载这个类实例，然后设置了一下不输出日志
    // 然后新起线程设置这个类实例，在新的线程中，是显示日志的。然后这个服务再也不显示日志了
    // if(sVO.noOutLog) {
    // ThreadSession.put("_nolog_","1");
    // }
    IScriptBean scriptObj = (IScriptBean) sVO.scriptObj; // 构建返回值
    if (sVO.singletonLevel > 0 && scriptObj != null) {
      // 返回常驻内存的类实例
      return scriptObj;
    }
    if (sVO.haveExtLibPaths && !sVO.independentClass) {
      // 这个脚本引用了扩展包路径，需要独立构造，通过反射方式调用
      // 不管是不是动作脚本，都用这个壳，这样能减少很多判断。也不影响服务类
      if (sVO.isActionBean) {
        scriptObj = new ActionScriptWrapper(sVO);
      } else {
        scriptObj = new ScriptWrapper(sVO);
      }
      if (sVO.singletonLevel > 0) {
        sVO.scriptObj = scriptObj;
      }
      return scriptObj;
    }
    try {
      // 构建类
      Class<?> cls = getScriptClass(sVO);
      if (cls == null) {
        warning("The Script LoadClass Is NULL\n" + sVO);
        return null;
      }
      if (cls.isInterface()) {
        return null;
      }
      if (sVO.independentClass) {
        // 不支持传统类用脚本方式获取类实例
        return null;
      }
      // 构建类实例
      Constructor<?> scriptCon = cls.getDeclaredConstructor(IScriptLoader.class, ScriptVO.class);
      scriptObj = (IScriptBean) scriptCon.newInstance(this, sVO);
      if (scriptObj == null) {
        warning("The Script newInstance Is NULL\n" + sVO);
        return null;
      }
      setScriptObjectFields(scriptObj); // 设置脚本类中的类变量值

      if (sVO.isClassVar && paraMap != null) {
        // 脚本中声明的传入参数，都是类变量参数
        setObjectField(scriptObj, sVO, paraMap);
      }
    } catch (Exception e) {
      e.printStackTrace();
      sVO.addErrorMsg("The Script ID:[" + sVO.id + "] new Instance Exception:" + DebugUtil.getExceptionInfo(e, "\n"));
      error("The Script ID:[" + sVO.id + "] new Instance Exception", e);
      throw new MsgException(this, sVO.errorMsg);
    }
    if (sVO.singletonLevel > 0) {
      // 此段代码应该执行不到，在启动时，就已经把常驻内存的类都构建好了
      sVO.scriptObj = scriptObj;
    }
    return scriptObj;
  }

  /**
   * 将参数容器中的值设置到指定脚本的类变量中
   * 
   * @param scriptObj 指定脚本类实例
   * @param vo        脚本信息类
   * @param para      参数容器 2020年9月9日
   * @author MBG
   */
  @SuppressWarnings("rawtypes")
  private void setObjectField(Object scriptObj, ScriptVO vo, Map para) {
    /*
     * 注意：目前只能设置 String,int,boolean 等常规的变量值
     */
    Field field; // 变量信息类
    Class cls = scriptObj.getClass(); // 脚本实例对应的类
    for (ScriptFieldVO fVO : vo.inFields) {
      try {
        field = cls.getDeclaredField(fVO.name);
        if (field == null) {
          continue;
        }
        field.setAccessible(true);
        if ("String".equals(fVO.type)) {
          field.set(scriptObj, str(para.get(fVO.name)));
        } else if ("int".equals(fVO.type)) {
          field.set(scriptObj, sint(para.get(fVO.name)));
        } else if ("boolean".equals(fVO.type)) {
          field.set(scriptObj, boo(para.get(fVO.name)));
        } else {
          field.set(scriptObj, para.get(fVO.name));
        }
      } catch (Exception e) {
      }
    }
  }

  /**
   * 注意：这个方法不能去掉，每个脚本不能使用自己的类加载器 （即：ScriptVO中内置这个脚本的类加载器），具体原因 请看ScriptVO中的
   * 注意（190121）
   * 
   * 获取脚本的类对象（很少用到，尽量不要用）
   * 
   * @param sVO 类信息容器
   * @return 脚本类对象
   * @throws Exception 异常 2016年7月27日
   * @author MBG
   */
  @Override
  public Class<?> getScriptClass(ScriptVO sVO) throws Exception {
    if (sVO.haveExtLibPaths && !sVO.independentClass) {
      if (sVO.isActionBean) {
        return ActionScriptWrapper.class;
      }
      return ScriptWrapper.class;
    }
    return scl.loadClass(sVO); // 构造脚本类;
  }

  /**
   * 重构当前脚本的类加载器（在脚本热替换以后，需要重构这个脚本的类加载器）
   * 
   * @param sVO 涉及到的脚本信息类 2019年1月16日
   * @author MBG
   */
  public void rebuildClassLoader(ScriptVO sVO) {
    if (scl == null) {
      // 不可能走到这里
      scl = new ScriptClassLoader(null, this, null);
    } else {
      scl.remove(SFilesUtil.getFilePath(getClassBasePath() + sVO.classFilePath),
          SFilesUtil.getFileBeforeName(sVO.classFilePath)); // 移除缓存
      scl = new ScriptClassLoader(scl, this, null);
    }
    sVO.scriptObj = null;
  }

  /**
   * 检查脚本内容是否发生变化，如果发生变化则更新脚本
   * 
   * @param sVO 脚本信息类
   * @throws Exception 异常 2014年8月22日
   * @author 马宝刚
   */
  protected void checkScriptChange(ScriptVO sVO) throws Exception {
    if (sVO.isSourceFileChanged()) {
      // 重新加载脚本
      info("\n++++++++++++++++++++++++++++++\nBegin Rebuild The Script ID:[" + sVO.id + "]");
      initScript(sVO, 2);
      info("\n++++++++++++++++++++++++++++++\n( ^3^ )╱~~  ScriptLoader Rebuild The Script ID:[" + sVO.id
          + "] Completed");
    } else if (sVO.isClassFileChanged()) {
      info("\n++++++++++++++++++++++++++++++\nBegin ReLoad The Script ID:[" + sVO.id + "]");
      initScript(sVO, 3);
      info(
          "\n++++++++++++++++++++++++++++++\n( ^3^ )╱~~  ScriptLoader ReLoad The Script ID:[" + sVO.id + "] Completed");
    }
    if (sVO.hasError()) {
      warning("The Script ID:[" + sVO.id + "] Source File Has Error:[" + sVO.errorMsg + "]", null);
    }
  }

  /**
   * 设置脚本类中的类变量值
   * 
   * @param scriptObj 脚本类 2014年7月29日
   * @author 马宝刚
   */
  protected void setScriptObjectFields(Object scriptObj) {
    if (scriptObj == null) {
      return;
    }
    if (scriptObj instanceof ABase) {
      ((ABase) scriptObj).setBase(this);
    }
    /*
     * 已通过构造函数设置 try { //设置脚本加载器 Field field =
     * ClassUtil.getDeclaredField("_scriptLoader",scriptObj.getClass());
     * field.setAccessible(true); field.set(scriptObj,this); }catch(Exception e) {
     * e.printStackTrace(); }
     */
  }

  /**
   * 初始化指定脚本（或重新初始化）
   * 
   * @param scriptID 脚本主键
   * @throws Exception 异常 2014年7月23日
   * @author 马宝刚
   */
  @Override
  public void initScript(String scriptID) throws Exception {
    // 获取指定的脚本类
    ScriptVO sVO = scriptMap.get(scriptID);
    if (sVO == null) {
      throw new MsgException(this, "The Scriopt ID:[" + scriptID + "] Has Not Found");
    }
    initScript(sVO, 2); // 执行初始化
    if (sVO.hasError()) {
      throw new MsgException(this, sVO.errorMsg);
    }
  }

  /**
   * 编译指定脚本
   * 
   * @param sVO   脚本信息类
   * @param state 0 启动时集中初始化 1 修改了脚本内容，重新初始化 2 从新初始化 3 编译后的类发生了变化，重新加载编译后的类 4
   *              修改了父类之后，需要重新编译全部继承该父类的子类，这时候不能传入2，否则就死循环了 2014年7月31日
   * @author 马宝刚
   */
  protected void compileScript(ScriptVO sVO, int state) {
    if (state != 3) {
      if (state == 2 || state == 4 || (sVO.sourceFileTime > 0 || sVO.sourceFileStatus != 0)
          && (sVO.classFileVer == null || !sVO.classFileVer.equals(sVO.sourceFileVer)) || sVO.loaderChanged()) {
        info("(^o^)Begin Compile Script:[" + sVO.id + "]");
        ScriptVO isVO; // 引用其它脚本信息类

        if (state == 0) {
          // 只有在项目启动时，检测引用脚本是否已经初始化
          // 如果没初始化，先初始化引用脚本
          for (String id : sVO.importScriptIdList) {
            if (sVO.id.equals(id)) {
              // warning("Fuck! The Script:["+sVO.id+"] Import It Self");
              continue;
            }
            isVO = scriptMap.get(id);
            if (isVO == null) {
              sVO.addErrorMsg("The Script ID:[" + sVO.id + "] import Other Script ID:[" + id + "] Not Found");
              return;
            }
            if (isVO.hasError()) {
              warning("The Script ID:[" + sVO.id + "] import Other Script ID:[" + id + "] Has Error:[" + isVO.errorMsg
                  + "]", null);
              return;
            }
            initScript(isVO, 0);
          }
        }

        // 检测扩展类路径是否存在
        if (sVO.haveExtLibPaths) {
          if (checkScriptExtLib(sVO)) {
            warning(sVO.errorMsg);
            return;
          }
        }

        sc.compile(sVO);
        if (sVO.isParent && state != 0) {
          // 如果修改了父类，凡是引用这个父类的子类都需要重新编译
          List<String> idList = getScriptIdList();
          ScriptVO osVO; // 循环脚本信息容器
          for (String id : idList) {
            osVO = getScriptInfo(id);
            if (osVO != null && sVO.id != null && sVO.id.equals(osVO.parentBeanID)) {
              // 这个脚本引用了改动后的父类
              compileScript(osVO, 4);
            }
          }
        }
      } else {
        // 无需编译
        sVO.updateSourceLastChanged();
      }
    }
    if (sVO.hasError()) {
      return;
    }
    try {
      // 热替换代码，重新加载类信息
      if (state != 0) {
        // 基础类加载器不能再一次define同一个类，必须new一个
        getScriptClass(sVO);
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
    if (state == 3) {
      // 在重新编译脚本时，已经更新了这个时间。但无需编译时，就得手动更新这个时间了
      sVO.updateClassFileTime();
    }
    /*
     * 只有在重新编译时，如果使用了脚本父类 需要先设置好脚本父类的类加载器，否则 在编译时找不到父类
     */
    // 只有在编译前才会遇到这种情况，也就是说
    // 是在存在源码脚本，知道父类主键的时候做的
    // loadClazz(sVO);
  }

  /**
   * 初始化传统类脚本
   * 
   * @param sVO 脚本信息容器 2016年4月8日
   * @author 马宝刚
   */
  @SuppressWarnings("unchecked")
  protected void initIndependentClass(ScriptVO sVO, Class<?> scriptCls) {
    if (sVO.haveExtLibPaths) {
      // 将传统类中用到的扩展包加载到全局类加载器中
      setIndependentExtLibPath(sVO.extLibPaths);
    }
    if (scriptCls == null) {
      try {
        scriptCls = getScriptClass(sVO);
      } catch (Exception e) {
        e.printStackTrace();
        sVO.addErrorMsg(e.getMessage());
        return;
      }
    }
    // 该处回写版本号已经废弃，从最初的读取类内部版本信息
    // 改为采用类的文件更新时间，后来发现这种方式导致本地跟
    // 远程脚本的版本总是对应不上。又改成了读取类内部版本信息
    // 该处已经废弃，已经在别处回写完毕
    // 回写类的更新时间（采用class的最后更新时间）
    // sVO.sourceFileVer = String.valueOf(sVO.classFileTime);
    // 先去掉以前的报错信息
    // sVO.errorMsg = null;
    // writeSourceFile(sVO); //写入文件

    if (!ClassUtil.implementsClass(scriptCls, IBean.class)) {
      // 只处理类加载器可以处理的类
      return;
    }
    // 注意，实际上并不是由类加载器构建的传统类脚本，因为类
    // 加载器的作用是：在初始化类后，按照配置信息
    _beanFactory.setClass((Class<IBean>) scriptCls, true);
  }

  /**
   * 将传统类中用到的扩展包加载到全局类加载器中
   * 
   * @param extPaths 扩展包相对路径 2020年5月12日
   * @author MBG
   */
  protected void setIndependentExtLibPath(String[] extPaths) {
    if (extPaths == null || extPaths.length < 1) {
      return;
    }
    // 搜索的文件信息序列
    List<String> fileList = new ArrayList<String>();
    for (int i = 0; i < extPaths.length; i++) {
      try {
        SFilesUtil.getFileList(fileList, infPath("/ext_lib/") + extPaths[i], null, null, true, false);
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
    for (String path : fileList) {
      _beanFactory.addClassPath(path);
    }
  }

  /**
   * 初始化（重新初始化）
   * 
   * @param sVO   脚本信息类
   * @param state 0启动时集中初始化 1修改了脚本内容，重新初始化 2从新初始化 3编译后的类发生了变化，重新加载编译后的类 2014年7月23日
   * @author 马宝刚
   */
  protected void initScript(ScriptVO sVO, int state) {
    if (state == 1) {
      // 脚本改变了内容时，当前的sVO并不是驻留在内存中的脚本信息，所以需要从内存中
      // 获取脚本对象
      destoryScript(scriptMap.get(sVO.id));

      // 在终止之前老的脚本以后，需要重新通过父类脚本判断该脚本是不是动作脚本
      isActionBean(sVO.id);
    } else if (state != 0) {
      // 启动时无需做注销
      destoryScript(sVO);
      // 在终止之前老的脚本以后，需要重新通过父类脚本判断该脚本是不是动作脚本
      isActionBean(sVO.id);
    }
    // 重新加载脚本信息
    if (state == 2) {
      // 在启动时都已经加载了最新的源信息，所以在此不必重复加载
      if (!loadSource(sVO)) {
        return;
      }
    }
    if (sVO.disabled) {
      log("The Script:[" + sVO.id + "] Has Set Disabled");
      // 删除目标编译类
      // 将相对路径转换为全路径
      String scriptClassPath = getClassBasePath() + sVO.classFilePath;
      // 删除该类对应的线程类 去掉尾部的 .class 扩展名，这样删除包括线程类文件和java源文件
      ScriptUtil.deleteScript(scriptClassPath.substring(0, scriptClassPath.length() - 6), false);
      return;
    }
    /*
     * 编译脚本（如果需要编译的话） 另外state==3时，只需要重新将类放到类加载器中
     */
    compileScript(sVO, state);
    if (sVO.hasError()) {
      // 存在错误，或者这个脚本引用了扩展包路径
      return;
    }
    // 构建脚本类
    Class<?> scriptCls = null;
    try {
      scriptCls = getScriptClass(sVO);
    } catch (Exception e) {
      e.printStackTrace();
      return;
    }
    if (scriptCls == null) {
      sVO.addErrorMsg("The Script ID:[" + sVO.id + "] loadClass Is NULL");
      error("The Script ID:[" + sVO.id + "] loadClass Is NULL");
      return;
    }
    if (scriptCls.isInterface()) {
      return;
    }
    if (sVO.independentClass) {
      // 不支持传统类用脚本方式获取类实例
      initIndependentClass(sVO, scriptCls); // 这一行之前是屏蔽掉的，不能屏蔽，因为保存传统类脚本需要调用到这里，需要初始化刚保存的传统类，并将传统类放到BeanFactory中
      return;
    }
    IScriptBean scriptObj = null; // 脚本类
    try {
      scriptObj = getScript(sVO);
    } catch (Exception e) {
      // 在内部已经抛了异常
      return;
    }
    // 初始化方法
    String initMethodName = getBeanClassInfo(sVO,0);
    if (initMethodName.length() > 0) {
      try {
        if (sVO.haveExtLibPaths && !sVO.independentClass) {
          // 执行带扩展包路径的初始化方法
          ((ScriptWrapper) scriptObj).runMethod(initMethodName);
        } else {
          // 获取初始化方法
          Method initMethod = scriptCls.getMethod(initMethodName);
          // 执行初始化方法
          initMethod.invoke(scriptObj);
        }
      } catch (Exception e) {
        e.printStackTrace();
        sVO.addErrorMsg("The Script ID:[" + sVO.id + "] Execute The Init Method:[" + initMethodName + "] Exception:"
            + DebugUtil.getExceptionInfo(e, "\n"));
        error("The Script ID:[" + sVO.id + "] Execute The Init Method Exception", e);
        return;
      }
    }
    if (state != 0) {
      // 初始化后调用的方法
      String afterStartMethodName = getBeanClassInfo(sVO,1);
      // 在启动初始化时，需要在全部都初始化后，在执行启动方法，所以不用在此执行
      if (afterStartMethodName.length() > 0) {
        try {
          if (sVO.haveExtLibPaths && !sVO.independentClass) {
            // 执行带扩展包路径的初始化方法
            ((ScriptWrapper) scriptObj).runMethod(afterStartMethodName);
          } else {
            // 获取初始化后执行的方法
            Method asMethod = scriptCls.getMethod(afterStartMethodName);
            // 执行初始化后执行的方法
            asMethod.invoke(scriptObj);
          }
        } catch (Exception e) {
          e.printStackTrace();
          sVO.addErrorMsg("The Script ID:[" + sVO.id + "] Execute The AfterStart Method:[" + afterStartMethodName
              + "] Exception:" + DebugUtil.getExceptionInfo(e, "\n"));
          error("The Script ID:[" + sVO.id + "] Execute The AfterStart Method Exception", e);
          return;
        }
      }
      // 调用类主键
      String registerID = getBeanClassInfo(sVO,3);
      if (registerID.length()>0) {
        try {
          // 因为脚本可以做热部署（重新构造初始化），如果服务主脚本（其它脚本都注册到该脚本）
          // 这个主脚本做了热部署，之前注册进来的脚本就都丢失了，无法再用。所以需要将注册
          // 进来的脚本主键保存到主脚本信息容器的这个序列中，在热部署之后，重新将这些脚本
          // 在注册进来
          if (hasScript(registerID)) {
            ScriptVO masterVO = getScriptInfo(registerID);
            if (masterVO.registerBeanIDs == null) {
              masterVO.registerBeanIDs = new ArrayList<String>();
            }
            if (!masterVO.registerBeanIDs.contains(registerID)) {
              masterVO.registerBeanIDs.add(registerID);
            }
            if (scriptObj == null) {
              // 只是为了不报错，通常这种注册没多大意义，因为注册主类不是主流内存的类
              scriptObj = getScript(sVO.id);
            }
            if (masterVO.haveExtLibPaths && !masterVO.independentClass) {
              // 通过反射方式注册
              ((ScriptWrapper) getScript(masterVO)).runMethod("regist", new Class[] { Object.class },
                  new Object[] { scriptObj });
            } else {
              ((IBeanRegister) getScript(masterVO)).regist(scriptObj);
            }
          } else if (getBeanFactory().beanExists(registerID)) {
            if (scriptObj == null) {
              // 只是为了不报错，通常这种注册没多大意义，因为注册主类不是主流内存的类
              scriptObj = getScript(sVO.id);
            }
            ((IBeanRegister) getBeanFactory().getObject(registerID, this)).regist(scriptObj);
          } else {
            sVO.addErrorMsg("The Script:[" + sVO.id + "] Execute Regist ID:[" + registerID + "] Not Find The Bean ID");
          }
        } catch (Exception e) {
          e.printStackTrace();
          sVO.addErrorMsg("The Script:[" + sVO.id + "] Execute Regist ID:[" + registerID + "] Exception:"
              + DebugUtil.getExceptionInfo(e, "\n"));
          error("The Script:[" + sVO.id + "] Execute Regist Exception", e);
        }
      }
      if (sVO.registerBeanIDs != null && scriptObj != null && scriptObj instanceof IBeanRegister) {
        Object bean; // 注册的脚本类实例
        for (String sId : sVO.registerBeanIDs) {
          info("---Begin Re-Regist Script:[" + sId + "] To The Script:[" + sVO.id + "]");
          try {
            // 获取需要注册的脚本类实例
            bean = getScript(sId);

            if (sVO.haveExtLibPaths && !sVO.independentClass) {
              // 加载了扩展包路径的脚本，只能通过反射方式注册
              ((ScriptWrapper) scriptObj).runMethod("regist", new Class[] { Object.class }, new Object[] { bean });
            } else {
              ((IBeanRegister) scriptObj).regist(bean); // 重新执行注册
            }

            info("( ^3^ )╱~~  Re-Regist Script:[" + sId + "] To The Script:[" + sVO.id + "] Completed");
          } catch (Exception e) {
            e.printStackTrace();
            sVO.addErrorMsg("Re-Regist Script:[" + sId + "] To This Script:[" + sVO.id + "] Exception:"
                + DebugUtil.getExceptionInfo(e, "\n"));
            error("Re-Regist Script:[" + sId + "] To This Script:[" + sVO.id + "] Exception", e);
          }
        }
      }
    }
  }

  /**
   * 判断父类（当前类）是否为动作类
   * @param pid     父类主键
   * @return        是否为动作类
   */
  private boolean isActionBean(String id){
    if(id==null || id.length()<1){
      return false;
    }
    if(hasScript(id)) {
      // 父类也是脚本主键
      ScriptVO svo = getScriptInfo(id, false);
      if (svo==null) {
        return false;
      }
      if(svo.interfaceClasspath!=null 
        && svo.interfaceClasspath.length()>0 
        && svo.interfaceClasspath.indexOf(IActionBean.class.getName())>-1){
        return true;
      }
      //递归调用
      svo.isActionBean = isActionBean(svo.parentBeanID);
      return svo.isActionBean;
    }else if(_beanFactory.beanExists(id)){
      // 类工厂管理的类信息容器
      BeanVO bvo = null;
      try{
        bvo = _beanFactory.getBeanVO(id,this);
      }catch(Exception e){}
      if (bvo==null) {
        return false;
      }
      return ClassUtil.implementsClass(bvo.beanClass, IActionBean.class);
    }
    return false;
  }

  /**
   * 通过脚本主键终止指定脚本类
   * 
   * @param scriptID 脚本主键
   * @throws Exception 异常 2014年7月23日
   * @author 马宝刚
   */
  @Override
  public void destoryScript(String scriptID) throws Exception {
    // 获取指定的脚本类
    ScriptVO sVO = scriptMap.get(scriptID);
    if (sVO == null) {
      throw new MsgException(this, "The Scriopt ID:[" + scriptID + "] Has Not Found");
    }
    destoryScript(sVO);
    if (sVO.hasError()) {
      throw new MsgException(this, sVO.errorMsg);
    }
  }
  
  /**
   * 终止脚本加载器
   * 2021年9月11日
   * @author MBG
   */
  public void stop() {
	  if(scs!=null) {
		  scs.stop();
	  }
	  //获取全部脚本主键序列
	  List<String> scriptIds = BaseUtil.getMapKeyList(scriptMap);
	  ScriptVO sVO;
	  for(String scriptId:scriptIds) {
		  sVO = scriptMap.get(scriptId);
		  if(sVO==null || sVO.singletonLevel<1) {
			  continue;
		  }
		  destoryScript(sVO);
	  }
  }

  /**
   * 执行销毁
   * 
   * @param sVO 脚本信息类 2014年7月23日
   * @author 马宝刚
   */
  protected void destoryScript(ScriptVO sVO) {
    if (sVO == null) {
      return;
    }
    if (sVO.disabled) {
      return;
    }
    // 构建脚本类
    Class<?> cls = null;
    try {
      cls = getScriptClass(sVO);
    } catch (Exception e) {
      e.printStackTrace();
      return;
    }
    if (cls == null || cls.isInterface()) {
      return;
    }
    if (sVO.independentClass) {
      // 不支持传统类用脚本方式获取类实例
      return;
    }
    String registerID = getBeanClassInfo(sVO,3);
    if (registerID != null && registerID.length() > 0) {
      IScriptBean scriptObj = null;
      if (scriptObj == null) {
        try {
          // 这里不能检测脚本是否发生变化，会造成死循环
          scriptObj = getScript(sVO, true, null);
        } catch (Exception e) {
          // 已经在内部抛了异常
          return;
        }
      }
      // 解析多个注册类主键
      List<String> registIdList = BaseUtil.splitToList(registerID, ",");
      for (String ele : registIdList) {
        try {
          if (getBeanFactory().beanExists(ele)) {
            ((IBeanRegister) getBeanFactory().getObject(ele, this)).unRegist(scriptObj);
          } else if (hasScript(ele)) {
            IScriptBean msObj = getScript(ele); // 注册主类脚本信息类
            if (msObj instanceof ScriptWrapper) {
              // 带扩展包类路径的脚本类，只能通过反射方式注销目标脚本
              ((ScriptWrapper) msObj).runMethod("unRegist", new Class[] { Object.class }, new Object[] { scriptObj });
            } else {
              ((IBeanRegister) msObj).unRegist(scriptObj);
            }
          }
        } catch (Exception e) {
          e.printStackTrace();
        }
      }
    }
    if (sVO.scriptObj != null) {
      // 终止的方法
      String destoryMethodName = getBeanClassInfo(sVO,2);
      if (destoryMethodName.length() > 0) {
        try {
          if (sVO.haveExtLibPaths && !sVO.independentClass) {
            ((ScriptWrapper) sVO.scriptObj).runMethod(destoryMethodName);
          } else {
            // 获取终止方法
            Method dsMethod = cls.getMethod(destoryMethodName);
            // 执行终止方法
            dsMethod.invoke(sVO.scriptObj);
          }
        } catch (Exception e) {
          e.printStackTrace();
          // sVO.addErrorMsg("The Script:["+sVO.id+"] Execute Destory
          // Method:["+destoryMethodName+"]
          // Exception:"+DebugUtil.getExceptionInfo(e,"\n"));
          error("The Script:[" + sVO.id + "] Execute Destory Method:[" + destoryMethodName + "] Exception", e);
        }
      }
      // 如果脚本中跑了线程，干掉这些线程
      ThreadManager.killThreadByClassNameKey(sVO.scriptObj.getClass().getName());
      sVO.scriptObj = null;
    }
  }

  /**
   * 加载编译后的内置脚本类 2017年2月3日
   * 
   * @author MBG
   */
  protected void searchNativeClass() {
    fm.load(rl); // 脚本文件管理类中加载类信息
    // 内置脚本文件路径是写死的，没必要写到配置文件中
    List<String> pathList = rl.getSubPathList("/script_classes/");
    ScriptVO sVO; // 脚本文件信息类
    String scriptId; // 脚本主键

    for (String path : pathList) {

      if (!path.endsWith(".class") || path.indexOf("$") > -1) {
        // 不加载内部类，后面自动会加载
        continue;
      }

      // 获取脚本主键
      scriptId = SFilesUtil.getFileBeforeName(path);
      if (scriptId.startsWith(ScriptVO.SCRIPT_CLASS_HEAD)) {
        // 如果有该主键，通常说明该脚本为传统类脚本，并不是那种ScriptVO.SCRIPT_CLASS_HEAD值开头的纯脚本
        scriptId = scriptId.substring(ScriptVO.SCRIPT_CLASS_HEAD.length());
      }

      // 先从容器中尝试获取内部编译后的脚本
      sVO = scriptMap.get(scriptId);
      if (sVO == null) {
        // 没有对应的源码
        sVO = new ScriptVO(this);
        sVO.nativeClass = true;
        sVO.hasNativeClass = true;
        sVO.nativeClassPath = path;
        sVO.sl = this;
        sVO.id = scriptId;
        sVO.className = scriptId;
        sVO.classPath = IScriptLoader.SCRIPT_PACKAGE + "." + sVO.className;
        sVO.subPath = SFilesUtil.getFilePath(path, false);
        if (sVO.subPath.endsWith("/")) {
          sVO.subPath = sVO.subPath.substring(0, sVO.subPath.length() - 1);
        }
        // 去掉开头的 /script_classes
        sVO.subPath = sVO.subPath.substring(15);
        // 没有脚本源文件，还拼装源文件路径干毛
        // sVO.sourceFilePath = "/"+sVO.subPath+"/"+sVO.className+".xml";
        sVO.classFilePath = "/" + sVO.subPath + "/" + sVO.className + ".class";
        sVO.buildSourceFilePath = "/" + sVO.subPath + "/" + sVO.className + ".java";
        SFilesUtil.createFile(getClassBasePath() + "/" + sVO.subPath + "/"); // 如果路径不存在，则自动建立该路径

        loadClazzInfo(sVO, true);

        scriptMap.put(sVO.id, sVO);
      } else {
        // 存在对应的源码
        sVO.nativeClass = true;
        sVO.hasNativeClass = true;
        sVO.nativeClassPath = path;

        loadClazzInfo(sVO, false);
      }
    }
  }

  /**
   * 搜索内置脚本源文件 2017年1月31日
   * 
   * @author MBG
   */
  protected void searchNativeSource() {
    // 内置脚本文件路径是写死的，没必要写到配置文件中
    List<String> pathList = rl.getSubPathList("/script_src/");

    ScriptVO sVO; // 脚本文件信息类
    String fileName; // 脚本主键//或传统类名
    String subPath; // 脚本相对路径
    String folderName = filesUtil.getFileBeforeName(SCRIPT_FOLDER_INFO_NAME); // 文件夹名（不带扩展名）
    for (String path : pathList) {
      fileName = filesUtil.getFileBeforeName(path);
      if (fileName == null || fileName.length() < 1 || fileName.equals(folderName)) {
        continue;
      }
      subPath = SFilesUtil.getFilePath(path, false);
      subPath = subPath.substring(11, subPath.length() - 1);
      sVO = scriptMap.get(fileName);
      if (sVO == null) {
        sVO = new ScriptVO(this);
        sVO.sl = this;
        sVO.id = fileName;
        sVO.nativeScript = true;
        sVO.hasNativeScript = true;

        //存在内置脚本，肯定存在编译后的脚本类
        sVO.hasNativeClass = true;
        sVO.nativeClass = true;
        
        sVO.subPath = subPath;
        sVO.sourceFilePath = "/" + subPath + "/" + fileName + ".xml";
        // 加载脚本信息
        if (!loadNativeSource(sVO, path)) {
          continue;
        }
        sVO.classFilePath = "/" + subPath + "/" + sVO.className + ".class";
        sVO.buildSourceFilePath = "/" + subPath + "/" + sVO.className + ".java";
        scriptMap.put(fileName, sVO);
      }
    }
  }

  /**
   * 执行搜索脚本源文件
   * 
   * @return 加载数量 2014年7月18日
   * @author 马宝刚
   */
  protected int searchSource() {
    int searchCount = 0; // 搜索并加载源码脚本的数量
    // 脚本源文件根路径
    String bSourcePath = getSourceBasePath();

    // 脚本源文件路径序列
    ArrayList<String> filePathList = search(bSourcePath, "Script Source", "xml");

    ScriptVO sVO; // 脚本文件信息类
    String fileName; // 脚本主键//或传统类名
    String subPath; // 脚本相对路径
    boolean disabledPath; // 是否禁用该文件夹
    String folderName = filesUtil.getFileBeforeName(SCRIPT_FOLDER_INFO_NAME); // 文件夹名（不带扩展名）
    for (String path : filePathList) {
      disabledPath = false;
      // 检测是否屏蔽掉该文件夹
      for (String disPath : disabledScriptPathList) {
        if (path.startsWith(disPath)) {
          disabledPath = true;
          break;
        }
      }
      if (disabledPath) {
        continue;
      }
      fileName = filesUtil.getFileBeforeName(path);
      if (fileName.equals(folderName)) {
        continue;
      }
      subPath = SFilesUtil.getFilePath(path, false);
      if (subPath.endsWith("/")) {
        subPath = subPath.substring(0, subPath.length() - 1);
      }
      sVO = scriptMap.get(fileName);
      if (sVO == null || sVO.nativeScript) {
        sVO = new ScriptVO(this);
        sVO.sl = this;
        sVO.id = fileName;
        sVO.subPath = subPath;
        sVO.sourceFilePath = "/" + subPath + "/" + fileName + ".xml";
        SFilesUtil.createFile(getClassBasePath() + "/" + subPath + "/"); // 如果路径不存在，则自动建立该路径
        // 加载脚本信息
        if (!loadSource(sVO)) {
          continue;
        }
        sVO.classFilePath = "/" + subPath + "/" + sVO.className + ".class";
        sVO.buildSourceFilePath = "/" + subPath + "/" + sVO.className + ".java";
        scriptMap.put(fileName, sVO);
        searchCount++;
      }
      checkRepeat(fileName, path); // 检测脚本是否重复 即使首次加载，也需要登记一下
    }
    return searchCount;
  }

  /**
   * 查询指定脚本被其它脚本所引用
   * 
   * @param scriptID 脚本主键
   * @return 被用的脚本信息序列 2014年10月24日
   * @author 马宝刚
   */
  @Override
  public List<ScriptVO> getForInclude(String scriptID) {
    // 构建返回值
    List<ScriptVO> reList = new ArrayList<ScriptVO>();
    // 脚本主键序列
    List<String> keyList = BaseUtil.getMapKeyList(scriptMap);
    ScriptVO ele; // 元素
    for (int i = 0; i < keyList.size(); i++) {
      ele = scriptMap.get(keyList.get(i));
      if (ele.importScriptIdList.contains(scriptID)) {
        reList.add(ele);
      }
    }
    return reList;
  }

  /**
   * 获取脚本类加载器 注意：这个方法由ScriptWrapper调用
   * 
   * @return 脚本类加载器 2019年1月22日
   * @author MBG
   */
  protected ScriptClassLoader getClassLoader() {
    return scl;
  }

  /**
   * 获取指定脚本被其它脚本引用的主键
   * 
   * @param scriptID 脚本主键
   * @return 被引用的脚本主键序列 2014年10月24日
   * @author 马宝刚
   */
  @Override
  public List<String> getForIncludeScriptIDList(String scriptID) {
    // 获取被引用的脚本VO序列
    List<ScriptVO> voList = getForInclude(scriptID);
    // 构建返回值
    List<String> reList = new ArrayList<String>();
    for (int i = 0; i < voList.size(); i++) {
      reList.add(voList.get(i).id);
    }
    return reList;
  }

  /**
   * 动态加载指定源脚本
   * 
   * @param scriptId 脚本主键
   * @param subPath  脚本相对路径 比如 0/0/1
   * @return 加载后的脚本信息 2015年5月21日
   * @author 马宝刚
   */
  @Override
  public ScriptVO loadSource(String scriptId, String subPath) {
    // 构建返回值
    ScriptVO reVO;
    if (scriptId == null || scriptId.length() < 1) {
      reVO = new ScriptVO(this);
      reVO.addErrorMsg("脚本主键为空，无法加载该脚本");
      return reVO;
    }
    reVO = scriptMap.get(scriptId);
    if (reVO == null) {
      reVO = new ScriptVO(this);
    } else {
      loadSource(reVO); // 重新加载脚本
      return reVO;
    }
    if (subPath == null || subPath.length() < 1) {
      reVO.addErrorMsg("脚本相对路径为空，无法定位到脚本位置");
      return reVO;
    }
    if (subPath.startsWith("/")) {
      subPath = subPath.substring(1);
    }
    if (subPath.endsWith("/")) {
      subPath = subPath.substring(0, subPath.length() - 1);
    }
    reVO.sl = this;
    reVO.id = scriptId;
    checkRepeat(scriptId, subPath); // 检测脚本是否重复 即使首次加载，也需要登记一下
    reVO.subPath = subPath;
    reVO.sourceFilePath = "/" + subPath + "/" + scriptId + ".xml";
    SFilesUtil.createFile(getClassBasePath() + "/" + subPath + "/"); // 如果路径不存在，则自动建立该路径
    // 加载脚本信息
    if (loadSource(reVO)) {
      reVO.classFilePath = "/" + subPath + "/" + reVO.className + ".class";
      reVO.buildSourceFilePath = "/" + subPath + "/" + reVO.className + ".java";
      initScript(reVO, 1); // 初始化脚本
      scriptMap.put(scriptId, reVO);
    }
    return reVO;
  }

  /**
   * 加载脚本信息
   * 
   * @param sVO 脚本信息类
   * @return 是否加载成功 2014年7月23日
   * @author 马宝刚
   */
  protected boolean loadSource(ScriptVO sVO) {
    // 清空错误信息
    sVO.resetError();
    try {
      sVO.updateSourceLastChanged();
    } catch (Exception e) {
      error("Get Script Source File:[" + getSourceBasePath() + sVO.sourceFilePath + "] Exception", e);
      return false;
    }
    IViewHandler vh; // 脚本对象处理类
    try {
      vh = FNodeHandler.newNodeHandler("UTF-8").setFilePath(getSourceBasePath() + sVO.sourceFilePath);
      vh.setXmlStyle(true); // 标记为处理xml内容
    } catch (Exception e) {
      e.printStackTrace();
      sVO.addErrorMsg("Load Script Source File:[" + getSourceBasePath() + sVO.sourceFilePath + "] Exception:"
          + DebugUtil.getExceptionInfo(e, "\n"));
      error("Load Script Source File:[" + getSourceBasePath() + sVO.sourceFilePath + "] Exception", e);
      return false;
    }
    sVO.setValueFromVh(vh); // 从xml中获取脚本信息
    sVO.inFields.addAll(staticFieldList); // 放入静态传入参数
    return true;
  }

  /**
   * 加载脚本信息
   * 
   * @param sVO 脚本信息类
   * @return 是否加载成功 2014年7月23日
   * @author 马宝刚
   */
  protected boolean loadNativeSource(ScriptVO sVO, String path) {
    sVO.errorMsg = null; // 清空错误信息
    IViewHandler vh; // 脚本对象处理类
    try {
      vh = FNodeHandler.newNodeHandler("UTF-8").setStream(rl.getFile(path), path);
      vh.setXmlStyle(true); // 标记为处理xml内容
    } catch (Exception e) {
      e.printStackTrace();
      sVO.addErrorMsg("Load Native Script Source File:[" + path + "] Exception:" + DebugUtil.getExceptionInfo(e, "\n"));
      error("Load Script Source File:[" + path + "] Exception", e);
      return false;
    }
    sVO.sourceFileTime = rl.getFileTime(path); // 设置文件最后更新时间
    sVO.setValueFromVh(vh); // 从xml中获取脚本信息
    sVO.inFields.addAll(staticFieldList); // 放入静态传入参数
    return true;
  }

  /**
   * 删除没有对应脚本的编译后的脚本类 2015年5月18日
   * 
   * @author 马宝刚
   */
  protected void checkLoadClass() {
    // 编译后的脚本根路径
    String bClassPath = getClassBasePath();
    bClassPath = BaseUtil.swapString(bClassPath, "\\", "/");
    if (bClassPath.endsWith("/")) {
      bClassPath = bClassPath.substring(0, bClassPath.length() - 1);
    }
    // 编译后的文件路径序列
    ArrayList<String> filePathList = search(bClassPath, "Script Class", "class");

    ScriptVO sVO; // 脚本信息元素
    String scriptId; // 脚本主键
    boolean disabledPath; // 是否禁用该文件夹
    for (String path : filePathList) {
      // path 例如：0/0/0/SN100004.class
      if (path.indexOf("$") > -1) {
        // 不加载内部类，后面自动会加载
        continue;
      }
      disabledPath = false;
      // 检测是否屏蔽掉该文件夹
      for (String disPath : disabledScriptPathList) {
        if (path.startsWith(disPath)) {
          disabledPath = true;
          break;
        }
      }
      if (disabledPath) {
        continue;
      }
      // 获取脚本主键
      scriptId = SFilesUtil.getFileBeforeName(path);
      if (classOnly) {
        // 没有脚本源文件，直接加载类

        // 先从容器中尝试获取内部编译后的脚本
        sVO = scriptMap.get(scriptId);
        if (sVO == null) {
          sVO = new ScriptVO(this);
        } else {
          sVO.nativeClass = false;
        }
        sVO.sl = this;
        sVO.id = scriptId;
        sVO.className = scriptId;
        sVO.classPath = IScriptLoader.SCRIPT_PACKAGE + "." + sVO.className;
        sVO.subPath = SFilesUtil.getFilePath(path, false);
        if (sVO.subPath.endsWith("/")) {
          sVO.subPath = sVO.subPath.substring(0, sVO.subPath.length() - 1);
        }
        // 没有脚本源文件，还拼装源文件路径干毛
        // sVO.sourceFilePath = "/"+sVO.subPath+"/"+sVO.className+".xml";
        sVO.classFilePath = "/" + sVO.subPath + "/" + sVO.className + ".class";
        sVO.buildSourceFilePath = "/" + sVO.subPath + "/" + sVO.className + ".java";
        SFilesUtil.createFile(getClassBasePath() + "/" + sVO.subPath + "/"); // 如果路径不存在，则自动建立该路径

        loadClazzInfo(sVO, true);

        scriptMap.put(sVO.id, sVO);
      } else {
        if (scriptId.startsWith(ScriptVO.SCRIPT_CLASS_HEAD)) {
          // 如果有该主键，通常说明该脚本为传统类脚本，并不是那种ScriptVO.SCRIPT_CLASS_HEAD值开头的纯脚本
          scriptId = scriptId.substring(ScriptVO.SCRIPT_CLASS_HEAD.length());
        }
        sVO = scriptMap.get(scriptId);
        if (sVO != null) {
          // 如果是内部脚本，或者搜索到的类文件与脚本源码文件中的路径一致
          if (sVO.nativeScript || (path.substring(0, path.lastIndexOf("/"))).equals(sVO.subPath)) {
            sVO.updateClassFileTime(); // 设置最新的编译类文件更新时间
            loadClazzInfo(sVO, false);
            continue;
          } else {
            // 这个class文件的相对路径跟脚本源文件相对路径不一致
            // 这种情况是存在的，比如迁移脚本到另外一个文件夹，class还留在原来的地方
            log.startLog("+++++++++++The ScriptClass:[" + scriptId + "] path:[" + path
                + "] Not Find The Script Source. Delete Class File");
            (new File(SFilesUtil.getAllFilePath(path, bClassPath))).delete();
            continue;
          }
        }
        log.startLog("+++++++++++++++++++++++++++++The ScriptClass:[" + scriptId + "] ClassPath:[" + path
            + "] Has Not Source File,Has Deleted++++++++++++++++++++++++++");
        // 将相对路径转换为全路径
        path = SFilesUtil.getAllFilePath(path, bClassPath);
        // 删除该类对应的线程类
        ScriptUtil.deleteScript(path.substring(0, path.length() - 6), false);
      }
    }
  }

  /**
   * 判断脚本是否重复
   * 
   * @param id   脚本主键
   * @param path 带文件名的路径 2015年5月15日
   * @author 马宝刚
   */
  private void checkRepeat(String id, String path) {
    int point = path.lastIndexOf("/");
    if (point > 0) {
      path = path.substring(0, point);
    }
    // 获取重复脚本信息序列
    List<String> rScriptList = repeatScriptMap.get(id);
    if (rScriptList == null) {
      rScriptList = new ArrayList<String>();
      repeatScriptMap.put(id, rScriptList);
    }
    if (!rScriptList.contains(path)) {
      rScriptList.add(path);
    }
  }

  /**
   * 读取类文件中的信息
   * 
   * @param sVO 类信息容器 2014年7月29日
   * @author 马宝刚
   */
  protected void loadClazzInfo(ScriptVO sVO, boolean allInfo) {
    // 获取脚本类文件对象
    ClassFile cf = fm.getClassFile(sVO);
    if (cf == null) {
      // 存在对应的外部脚本
      return;
    }
    // 类信息
    String[] clsInfos = ClassUtil.getClassInfo(cf);
    // if(sVO.hasSourceFile) {
    // 如果存在脚本源文件，判断是否为传统类脚本

    // 如果是传统类脚本，类的版本号就是class文件的最后更新时间
    // 因为类的内部可能人为忘写了ClassInfo声明信息，或者可能忘记修改
    // 注意：还是采用了类内部的的版本号，否则本地版本跟远程版本总是对不上
    // if(sVO.independentClass) {
    // sVO.classFileVer = String.valueOf((new
    // File(getClassBasePath()+sVO.classFilePath)).lastModified());
    // }else {
    // sVO.classFileVer = clsInfos[0];
    // }
    // }else {
    // sVO.classFileVer = clsInfos[0];
    // }
    sVO.classFileVer = clsInfos[0];
    sVO.title = clsInfos[1];

    // bean信息
    clsInfos = ClassUtil.getBeanInfo(cf);
    // 注意：脚本主键并不是类主键。虽然纯脚本类中这两个值是相同的，
    // 但是传统类脚本的脚本主键是类名，并不是类主键
    if (ClassUtil.implementsClass(cf, IScriptBean.class)) {
      if (clsInfos[0] != null && clsInfos[0].length() > 0) {
        sVO.id = clsInfos[0];
      }
    } else {
      // 类名在上一级中就已经设置好了
      sVO.id = sVO.className;
      sVO.independentClass = true;
    }
    sVO.isParent = SBoolean.valueOf(clsInfos[1]);
    sVO.parentBeanID = clsInfos[2];

    // 运行时信息
    clsInfos = ClassUtil.getBeanRunning(cf);
    sVO.singletonLevel = SInteger.valueOf(clsInfos[0]);
    sVO.afterStartMethodName = clsInfos[1];
    sVO.initMethodName = clsInfos[2];
    sVO.destoryMethodName = clsInfos[3];

    // 注册信息
    sVO.registerID = ClassUtil.getBeanRegisterID(cf);
    // 没必要在初始化时设置注册类主键，因为在编辑脚本的时候会显示父类的注册类主键
    // 再保存的时候，就保存到当前脚本中了。这样就写死了注册类主键，将来更换时，要
    // 修改很多个动作脚本，相当麻烦
    // if(sVO.registerID==null || sVO.registerID.length()<1) {
    // sVO.registerID = ClassUtil.getBeanRegisterID(sCls.getSuperclass());
    // }
    // 是否为影子类
    sVO.isShadow = ClassUtil.getBeanIsShadow(cf);
    setClassInfo(sVO, cf, allInfo);
    // 设置脚本扩展库路径信息
    sVO.setExtLibs(ClassUtil.getExtLib(cf));
  }

  /**
   * 递归设置类信息
   * 
   * @param cls          指定脚本类
   * @param sVO          脚本信息容器
   * @param allClassInfo 是否加载全部类中的脚本信息 2016年12月13日
   * @author MBG
   */
  private void setClassInfo(ScriptVO sVO, ClassFile cf, boolean allClassInfo) {
    if (cf == null) {
      return;
    }
    // Class文件中的脚本信息
    String[] scriptInfos = ClassUtil.getScriptInfo(cf);
    if (allClassInfo) {
      // 递归设置父类信息
      String[] beanInfos = ClassUtil.getBeanInfo(cf);
      boolean setParentInfo = false; // 是否设置类父类信息
      if (beanInfos != null) {
        if (beanInfos.length > 2 && beanInfos[2] != null && beanInfos[2].length() > 0) {
          // 尝试获取父类信息类
          ScriptVO parentVO = scriptMap.get(beanInfos[2]);
          if (parentVO != null) {
            setClassInfo(sVO, fm.getClassFile(parentVO), allClassInfo);
            setParentInfo = true;
          }
        }
        if (!setParentInfo && beanInfos.length > 5 && beanInfos[5] != null && beanInfos[5].length() > 0) {
          setClassInfo(sVO, fm.getClassFile(beanInfos[5]), allClassInfo);
          setParentInfo = true;
        }
      }
      sVO.setClassInfo(scriptInfos);
    } else if (scriptInfos != null) {
      // 加载脚本源文件中没有的信息
      if (scriptInfos.length > 0 && scriptInfos[0] != null) {
        sVO.loaderVer = scriptInfos[0];
      }
    }
  }

  /**
   * 搜索指定类型的文件
   * 
   * @param basePath 搜索根路径
   * @param logInfo  日志提示信息
   * @param extName  扩展名
   * @return 搜索到符合条件的文件路径 2014年7月16日
   * @author 马宝刚
   */
  protected ArrayList<String> search(String basePath, String logInfo, String extName) {
    // 记录加载时间
    Object time = log.runBefore();

    // 构建返回值
    ArrayList<String> reList = new ArrayList<String>();
    if (basePath == null || basePath.length() < 1 || !(new File(basePath)).exists()) {
      warning("Start Scan " + logInfo + " Warning: The Path [" + basePath + "] Not Exists", null);
      return reList;
    }
    log.startLog("Start Scan " + logInfo + " from [" + basePath + "]...");
    try {
      SFilesUtil.getFileList(reList, basePath, null, extName, true, true, boo(p("disabled_file_valid")));
    } catch (Exception e) {
      e.printStackTrace();
      error("Scan " + logInfo + " error,ClassBasePath:[" + basePath + "]", e);
      return reList;
    }
    // 获取路径下配置文件数量
    int fileCount = reList.size();
    log.startLog("Total Search " + logInfo + " Files [" + fileCount + "] Count");
    log.writeRuntime(time, "Scan " + logInfo + " complete");
    return reList;
  }

  /**
   * 获取源文件根路径 要求路径分隔符都为 / 并且末尾没有分隔符
   * 
   * @return 源文件根路径 2014年7月8日
   * @author 马宝刚
   */
  @Override
  public String getSourceBasePath() {
    if (srcPath == null) {
      srcPath = p("scripts_source_base_path");
      if (srcPath.length() < 1) {
        srcPath = p("scripts/source_base_path");
      }
      srcPath = BaseUtil.swapString(infPath(srcPath), "\\", "/");
      if (srcPath.endsWith("/")) {
        srcPath = srcPath.substring(0, srcPath.length() - 1);
      }
    }
    return srcPath;
  }

  /**
   * 获取有效的编译后的程序跟路径 要求路径分隔符都为 / 并且末尾没有分隔符
   * 
   * @return 有效的编译后的程序跟路径 2014年7月8日
   * @author 马宝刚
   */
  @Override
  public String getClassBasePath() {
    if (binPath == null) {
      binPath = p("scripts_class_base_path");
      if (binPath.length() < 1) {
        binPath = p("scripts/class_base_path");
      }
      binPath = BaseUtil.swapString(infPath(binPath), "\\", "/");
      if (binPath.endsWith("/")) {
        binPath = binPath.substring(0, binPath.length() - 1);
      }
    }
    return binPath;
  }

  /**
   * 获取备份根路径
   * 
   * @return 备份根路径 2014年8月25日
   * @author 马宝刚
   */
  @Override
  public String getBakBasePath() {
    if (bakPath == null) {
      bakPath = p("scripts_class_bak_path");
      if (bakPath.length() < 1) {
        bakPath = p("scripts/class_bak_path");
      }
      bakPath = BaseUtil.swapString(infPath(p("file_sources") + "/" + bakPath), "\\", "/");
      if (bakPath.endsWith("/")) {
        bakPath = bakPath.substring(0, bakPath.length() - 1);
      }
    }
    return bakPath;
  }

  /**
   * 获取调用者名字
   * 
   * @param invoker 调用者类实例
   * @return 调用者名字 2015年5月25日
   * @author 马宝刚
   */
  private String getInvokerName(Object invoker) {
    if (invoker == null) {
      return "";
    }
    if (invoker instanceof IScriptBean) {
      return ((IScriptBean) invoker).getScriptId();
    }
    return invoker.getClass().getName();
  }

  /**
   * 调用指定路由脚本 脚本不是本机脚本，而是通过ProgramRouteFilter程序路由类（分布式运行）调用的远程服务器的脚本
   * 
   * @param groupKey      集群群组主键
   * @param invoker       调用者（用来在日志中显示）
   * @param scriptID      目标脚本主键
   * @param inParaMap     传入参数
   * @param invokeType    调用方式 0普通调用 1调用调用主服务器 2广播调用
   *                      （在这个方法中，0也是只调用主服务器，因为路由到目标主服务器中）
   * @param noReturnValue 是否不需要返回值 （因为是目标服务器，本机是无法通过脚本信息类判定目标脚本带不带返回值，只能手工强制无需返回值）
   *                      这中情况主要用于反向应触发调用服务，只要通知前置机，无需获取返回值。这样就无需轮询等待返回值，使远程调用
   * @return 返回值
   * @throws Exception 异常 2017年4月21日
   * @author MBG
   */
  @Override
  @SuppressWarnings("rawtypes")
  public Object invokeRouteScript(String groupKey, Object invoker, String scriptID, Map inParaMap, int invokeType,
      boolean noReturnValue) throws Exception {
    if (cluster == null) {
      warning("---invokeRouteScript The Cluster Is NULL. groupKey:[" + groupKey + "] scriptID:[" + scriptID + "]");
      return null;
    }
    if (cluster.disabled()) {
      return invokeScript(invoker, scriptID, inParaMap, invokeType);
    }
    return cluster.routeCall(groupKey, scriptID, inParaMap, invokeType, getInvokerName(invoker), noReturnValue);
  }

  /**
   * 调用指定的脚本
   * 
   * @param invoker   调用者类实例
   * @param scriptID  脚本主键
   * @param inParaMap 传入参数容器
   * @return 脚本返回值
   * @throws Exception 异常 2014年7月29日
   * @author 马宝刚
   */
  @Override
  @SuppressWarnings("rawtypes")
  public <T> T invokeScript(Object invoker, String scriptID, Map inParaMap) throws Exception {
    return invokeScript(invoker, scriptID, inParaMap, 0);
  }

  /**
   * 调用指定的脚本
   * 
   * @param invoker    调用者类实例
   * @param scriptID   脚本主键
   * @param inParaMap  传入参数容器
   * @param invokeType 0普通调用 1调用主服务器 2广播调用 99由集群发起，响应外部服务器调用
   * @return 脚本返回值
   * @throws Exception 异常 2014年7月29日
   * @author 马宝刚
   */
  @Override
  @SuppressWarnings({ "rawtypes" })
  public <T> T invokeScript(Object invoker, String scriptID, Map inParaMap, int invokeType) throws Exception {
    return invokeScript(invoker, scriptID, inParaMap, invokeType, false);
  }

  /**
   * 调用指定的脚本
   * 
   * @param invoker       调用者类实例
   * @param scriptID      脚本主键
   * @param inParaMap     传入参数容器
   * @param invokeType    0普通调用 1调用主服务器 2广播调用 99由集群发起，响应外部服务器调用
   * @param noReturnValue （用于远程方法调用）如果远程接口没有返回值，在这里标记无需返回值，这样可以提高处理效率
   * @return 脚本返回值
   * @throws Exception 异常 2014年7月29日
   * @author 马宝刚
   */
  @Override
  @SuppressWarnings({ "rawtypes", "unchecked" })
  public <T> T invokeScript(Object invoker, String scriptID, Map inParaMap, int invokeType, boolean noReturnValue)
      throws Exception {
    if (!scriptMap.containsKey(scriptID) && rsl != null && rsl.enabled() && invokeType != 99) {
      // 尝试获取该脚本所在的集群分组名
      String groupId = rsl.serverGroup(scriptID);
      if (groupId != null && groupId.length() > 0) {
        // 存在远程脚本
        return (T) cluster.routeCall(groupId, scriptID, inParaMap, invokeType, invoker, noReturnValue);
      }
    }
    String invokerName = getInvokerName(invoker); // 获取调用者名字
    long beforeTime = System.currentTimeMillis(); // 记录执行开始时间
    ScriptVO sVO = getScriptInfo(scriptID, false); // 获取对应的脚本信息
    if (sVO == null) {
      warning("Script:[" + scriptID + "]Not Found.Invoker:[" + invokerName + "]");
      return null;
    }
    if (sVO.disabled) {
      warning("Script:[" + scriptID + "]Has Disabled.Invoker[" + invokerName + "]");
      return null;
    }
    boolean noLog = false; // 是否准备运行脚本前，禁用日志输出
    info("Begin Invoke:[" + scriptID + "] Invoker:[" + invokerName + "]");
    if (!boo(ThreadSession.get("_nolog_")) && sVO.noOutLog) {
      // 如果当前允许输出日志并且当前要运行的脚本禁止输出日志
      noLog = true;
      outLog(false);
    } else {
      log.debug("Invoke Script:[" + scriptID + "] Invoker:[" + invokerName + "] InPara:["
          + DebugUtil.getMapValue(inParaMap, " ") + "]");
    }
    // 注意，凡是集群管理类调用的，都不进入集群处理，避免死循环
    if (invokeType != 0 && cluster != null && cluster.clusterMode() && !cluster.equals(invoker)) {
      // 可以由服务调用集群中的动作

      // 集群模式 （如果当前服务器是主服务器，就不能用集群调用，否则会死循环）
      if (invokeType == 1 && !cluster.master()) {
        // 调用主服务器动作
        return (T) cluster.call(sVO.id, inParaMap, 1);
      } else if (invokeType == 2) {
        // 广播集群中所有服务器动作

        // 执行广播，但是不广播到自己
        cluster.callAll(sVO.id, inParaMap);

        // 不返回，执行本机动作
        // return null;
      }
    }
    return invokeScript(sVO, inParaMap, invoker, invokerName, invokeType, null, noLog, beforeTime);
  }

  /**
   * 调用子脚本动作类
   * 
   * @param ab              子脚本动作类
   * @param invoker         调用的父脚本动作类
   * @param paraMap         传入参数容器（优先取该容器中的值，如果容器中不存在该值，再从request中获取） 如果传入参数中存在
   *                        _no_out 的值为1 ，则不输出子脚本中的信息到页面
   * @param callFromCluster 是否来自集群调用 如果是集群调用，则不会调用beforeRunAction和afterRunAction
   * @param ac              动作上下文
   * @return 调用返回值
   * @throws Exception 异常 2015年3月4日
   * @author 马宝刚
   */
  @SuppressWarnings({ "unchecked", "rawtypes" })
  private <T> T invokeActionScript(IActionBean ab, Object invoker, Map<String, Object> paraMap, boolean callFromCluster,
      IActionContext ac) throws Exception {
    if (paraMap == null) {
      paraMap = new HashMap();
    }
    // 获取调用者名字
    String invokerName = getInvokerName(invoker);
    if (invoker instanceof IActionBean) {
      ab.setActionContext(((IActionBean) invoker).getActionContext());
    } else {
      if (ac != null) {
        ab.setActionContext(ac);
      }
    }
    try {
      // 构建传入参数
      ab.buildActionParameter(paraMap);

      if (callFromCluster || ab.beforeRunAction()) { // 调用动作前执行
        ThreadSession.put("_invoker_", invokerName); // 将调用者名字放入线程会话中
        if (callFromCluster) {
          // 输出类型，只允许1xml 2json（默认）
          int contentType = sint(paraMap.get("_oct"));
          ab.setContentType(contentType == 1 ? 1 : 2);
        }
        if (ab instanceof IScriptInvokeEvent) {
          // 如果这个脚本实现了脚本运行事件接口
          if (((IScriptInvokeEvent) ab).beforeInvoke(invoker, paraMap) == null) {
            ab.invokeAction();
            ((IScriptInvokeEvent) ab).afterInvoke(invoker, paraMap, null);
          }
        } else {
          ab.invokeAction();
        }
        if (callFromCluster) {
          // 在集群远程调用这个动作脚本时，这个动作脚本如果没有定义返回值，则将这个动作脚本输出的信息 返回到远程服务器中
          Object res; // 构建返回值
          switch (ab.getOutContentType()) {
            case 0:
              res = ab.getReVh();
              break;
            case 1:
              res = ab.getReXml();
              if (((IViewHandler) res).ocnn("status").size() < 1) {
                ((IViewHandler) res).ac("status").nt("1");
              }
              break;
            default:
              res = ab.getReJson();
              if (!((Json) res).isList() && !((Json) res).containsKey("status")) {
                // 设置默认返回值
                ((Json) res).put("status", "1");
              }
              break;
          }

          // 集群调用的话，在这里返回
          return (T) res;
        } else {
          ab.afterRunAction(); // 调用动作后执行
        }
      }
    } catch (FixException e) {
      ab.errorAction(e); // 发生了异常
      throw e;
    } catch (Exception e) {
      e.printStackTrace();
      error("Invoke Action [" + ab + "] Invoker:[" + invokerName + "] Exception", e);
      ab.errorAction(e); // 发生了异常
      throw e;
    }
    if (!SBoolean.valueOf(paraMap.get("_no_out"))) {
      if (!ab.isNoReturnInfo() && (invoker instanceof IActionBean)) {
        // 返回值类型
        int cType = ab.getOutContentType();
        if (cType == 1) {
          ((IActionBean) invoker).getReXml().addChildNode(ab.getReXml());
        } else if (cType == 0) {
          if (ab.loadAllPage()) {
            // 将父块动作所操作的页面清空，替换成子动作的整个页面
            // 然后再终止父动作的后续块动作，因为页面都换了，后续的动作处理的旧页面都不存在了。
            ((IActionBean) invoker).getReVh().getBaseNode().clear().os(false).ac(ab.getReVh());
            ((IActionBean) invoker).over(true);
          } else {
            ((IActionBean) invoker).getReVh().addChildNode(ab.getReVh());
          }
        } else {
          // 获取返回的json信息
          Json reJson = ab.getReJson();
          if (reJson.isList()) {
            ((IActionBean) invoker).getReJson().getChildList().addAll(ab.getReJson().getChildList());
          } else {
            ((IActionBean) invoker).getReJson().putValueMap(reJson.getValueMap());
          }
        }
      }
    }
    return null;
  }

  /**
   * 通过源文件相对路径获取源文件内容对象 如果获取的源文件存在错误，可以通过error_msg获取错误信息
   * 
   * @param subPath 相对路径
   * @return 源文件对象 2014年8月4日
   * @author 马宝刚
   */
  @Override
  public IViewHandler getSourceVh(String subPath) {
    // 通过文件名获取对应的脚本主键
    String scriptId = filesUtil.getFileBeforeName(subPath);
    // 获取对应的脚本信息类
    ScriptVO sVO = scriptMap.get(scriptId);

    // 构建返回值
    INodeHandler nh = FNodeHandler.newNodeHandler("UTF-8");
    nh.setXmlStyle(true);
    if (sVO.hasError()) {
      nh.getFirstChildNodeByName("root").addNewChildNode("error_msg").setNodeCdata(sVO.errorMsg);
    } else {
      try {
        nh.setFilePath(getSourceBasePath() + sVO.sourceFilePath);
        // 设置脚本主键
        nh.getFirstChildNodeByNodeName("root").addNewChildNode("id").nt(scriptId);
        // 设置源码根路径
        nh.getFirstChildNodeByNodeName("root").addNewChildNode("sub_path").nt(sVO.subPath);
      } catch (Exception e) {
        e.printStackTrace();
        nh.getFirstChildNodeByName("root").addNewChildNode("error_msg").setNodeCdata(sVO.errorMsg);
      }
    }
    return nh;
  }

  /**
   * 更新源文件内容，并重新编译 如果保存源文件时存在错误，可以通过error_msg获取错误信息
   * 
   * @param sourceVh      即将保存的源文件对象
   * @param reBuildSource 是否为了返回编译后的源码 2014年8月4日
   * @author 马宝刚
   */
  @Override
  public ScriptVO saveSourceVh(IViewHandler sourceVh) {
    // 构建刚提交的类信息
    ScriptVO sVO = new ScriptVO(this, sourceVh);
    sVO.sl = this;
    if (sVO.id != null && sVO.id.length() > 0) {
      // 先获取原来的脚本信息
      ScriptVO loadedSVO = scriptMap.get(sVO.id);
      if (loadedSVO != null && loadedSVO.nativeScript && !enabledSaveNativeScript) {
        sVO.addErrorMsg("禁止保存内部脚本");
        return sVO;
      }
      if (loadedSVO != null) {
        sVO.setStatusValue(loadedSVO);
      }
    }
    saveSource(sVO);
    return sVO;
  }

  /**
   * 获取编译后的脚本源码（支持新增的脚本）
   * 
   * @param sourceVh 即将保存的源文件对象
   * @param outError 是否输出编译错误信息
   * @return 代码内容 2014年8月25日
   * @author 马宝刚
   */
  @Override
  public String getBuildSourceContent(IViewHandler sourceVh, boolean outError) {
    // 构建刚提交的类信息
    ScriptVO sVO = new ScriptVO(this, sourceVh);
    if (sVO.id == null || sVO.id.length() < 1) {
      // 脚本头值（只允许大写字母）
      sVO.id = "CRIPT_SOURCE"; // 注意：会自动加上大写的S
    }
    // 返回脚本源码
    String resContent = sc.outSource(sVO);
    if (outError && sVO.hasError()) {
      return resContent + "\n\n\n\n" + sVO.errorMsg;
    }
    if (resContent.length() < 1) {
      // 没有编译内容，肯定报错了
      return sVO.errorMsg;
    }
    return resContent;
  }

  /**
   * 保存传统独立类源文件
   * 
   * @param sVO 脚本信息类 2016年4月8日
   * @author 马宝刚
   */
  protected void saveIndependentSource(ScriptVO sVO) {
    if (sVO.subPath == null || sVO.subPath.length() < 1) {
      sVO.addErrorMsg("没有找到脚本保存路径");
      return;
    }
    if (!sVO.independentClass) {
      sVO.addErrorMsg("该类并不是传统独立类");
      return;
    }
    // 通过自定义的类名获取对应的脚本
    ScriptVO oScriptVO = scriptMap.get(sVO.id);
    if (oScriptVO != null && oScriptVO.subPath != null) {
      // 之前遇到了两个相对路径，实际上是一样的，但其中一个末尾多了个 / 导致被认成了
      // 不在同一个路径中
      String path1 = BaseUtil.trim(oScriptVO.subPath, "/"); // 已有脚本
      String path2 = BaseUtil.trim(sVO.subPath, "/"); // 正在保存的脚本
      if (!path1.equals(path2)) {
        // 保存的脚本与现有脚本重复了
        sVO.addErrorMsg(
            "保存的传统类脚本的类名[" + sVO.id + "]与已有的传统类脚本重复 当前脚本路径：[" + sVO.subPath + "] 已有脚本路径：[" + oScriptVO.subPath + "]");
        return;
      }
      sVO.sourceFileStatus = 2; // 更新
      sVO.uCount = oScriptVO.uCount;
      if (sVO.modifyContent == null || sVO.modifyContent.length() < 1) {
        sVO.modifyContent = oScriptVO.modifyContent;
      }
    } else {
      sVO.sourceFileStatus = 1; // 新增
    }
    sVO.sourceFilePath = "/" + sVO.subPath + "/" + sVO.id + ".xml";
    sVO.buildSourceFilePath = "/" + sVO.subPath + "/" + sVO.id + ".java";
    sVO.classFilePath = "/" + sVO.subPath + "/" + sVO.id + ".class";
    if (!sVO.isSyncSubmit) {
      sVO.sourceFileVer = SDate.nowDateTimeWithoutSecond();
      if (sVO.uCount == null || sVO.uCount.length() < 1) {
        sVO.uCount = "1";
      } else {
        sVO.uCount = String.valueOf(sint(sVO.uCount) + 1);
      }
    }
    if (sVO.noSingleCompile) {
      // 只针对本次保存，不进行编译
      sVO.noSingleCompile = false;
    } else {
      // 标记编译后不自动回写传统类版本号，而是在该方法末尾统一保存
      sVO.independentClassAfterSave = true;
      try {
        initScript(sVO, 1); // 初始化脚本
      } catch (RuntimeException e) {
        e.printStackTrace();
        sVO.addErrorMsg(DebugUtil.getExceptionInfo(e, "\n"));
      } catch (Exception e) {
        e.printStackTrace();
        sVO.addErrorMsg(DebugUtil.getExceptionInfo(e, "\n"));
      }

      if (sVO.hasError()) {
        if (sVO.sourceFileStatus == 1) {
          // 删除编译失败的脚本信息
          ScriptUtil.deleteScript(SFilesUtil.getAllFilePath(sVO.subPath + "/" + sVO.id, getClassBasePath()), true); // 删除相关的线程类
        } else if (sVO.sourceFileStatus == 2) {
          // 更新文件失败，准备还原上次正确的

          // 上次正确的脚本容器
          ScriptVO lastVO = scriptMap.get(sVO.id);
          if (lastVO != null) {
            // 肯定不为空
            initScript(lastVO, 1);
          }
        }
        // 出错了
        return;
      }
    }

    // 从编译的传统类中的注解中获取脚本配置信息，比如是否为父类，注册类主键等等
    loadClazzInfo(sVO, true);

    writeSourceFile(sVO); // 保存传统类脚本到文件

    // 当前全部脚本主键序列
    List<String> idList = BaseUtil.getMapKeyList(scriptMap);
    // 作为父类刷新子类脚本
    refreshChildClass(sVO, idList);
    // 递归刷新引用了该传统类的其它脚本
    refreshForInclude(sVO.id, idList, new ArrayList<String>(), true);

  }

  /**
   * 刷新继承了当前类的所有脚本
   * 
   * @param sVO    当前类的信息类
   * @param idList 所有脚本主键序列 2020年5月14日
   * @author MBG
   */
  @SuppressWarnings("rawtypes")
  private void refreshChildClass(ScriptVO sVO, List<String> idList) {
    if (!sVO.isParent) {
      return;
    }
    // 获取真实的类主键
    String beanId = null; // 真实的类主键
    try {
      // 构建对应的类
      Class cls = scl.loadClass(sVO);
      // 获取类中保存的类主键信息
      String[] infos = ClassUtil.getBeanInfo(cls);
      if (infos == null || infos.length < 1) {
        return;
      }
      beanId = infos[0];
    } catch (Exception e) {
      e.printStackTrace();
      return;
    }
    if (beanId == null || beanId.length() < 1) {
      return;
    }
    String regId; // 注册到目标类的主键
    List<String> regIdList; // 注册到目标类主键序列
    ScriptVO cVO; // 脚本信息类
    Object scriptObj; // 脚本类实例
    Object msObj; // 注册到目标类实例
    for (String id : idList) {
      cVO = scriptMap.get(id);
      if (cVO == null || cVO.parentBeanID == null || !cVO.parentBeanID.equals(beanId)) {
        continue;
      }

      cVO.scriptObj = null; // 清空已经构造的类实例
      scl.remove(cVO); // 清空类缓存

      if (cVO.registerID!=null && cVO.registerID.length()>0) {
        regId = cVO.registerID;
      } else if (sVO.registerID!=null && sVO.registerID.length() > 0) {
        regId = sVO.registerID;
      } else {
        regId = null;
      }
      if(regId != null && regId.length() > 0) {
        try {
          if (cVO.independentClass) {
            scriptObj = bean(getScriptClass(cVO));
          } else {
            scriptObj = getScript(cVO);
          }
          regIdList = BaseUtil.splitToList(regId, ",");
          for (String rid : regIdList) {
            if (rid == null || rid.length() < 1) {
              continue;
            }
            // 执行重新注册
            if (getBeanFactory().beanExists(rid)) {
              msObj = getBeanFactory().getObject(rid, this);
              ((IBeanRegister) msObj).unRegist(scriptObj);
              ((IBeanRegister) msObj).regist(scriptObj);
            } else if (hasScript(rid)) {
              msObj = getScript(rid); // 注册主类脚本信息类
              if (msObj instanceof ScriptWrapper) {
                // 带扩展包类路径的脚本类，只能通过反射方式注销目标脚本
                ((ScriptWrapper) msObj).runMethod("unRegist", new Class[] { Object.class }, new Object[] { scriptObj });
                ((ScriptWrapper) msObj).runMethod("regist", new Class[] { Object.class }, new Object[] { scriptObj });
              } else {
                ((IBeanRegister) msObj).unRegist(scriptObj);
                ((IBeanRegister) msObj).regist(scriptObj);
              }
            }
          }
        } catch (Exception e) {
          e.printStackTrace();
        }
      }
    }

  }

  /**
   * 递归新引用了该传统类的其它常驻内存脚本
   * 
   * @param scriptId 指定脚本主键
   * @param idList   全部脚本主键序列
   * @param okList   处理后的脚本主键序列
   * @param isFirst  是否首次调用 2020年5月12日
   * @author MBG
   */
  private void refreshForInclude(String scriptId, List<String> idList, List<String> okList, boolean isFirst) {
    if (isFirst) {
      okList.add(scriptId);
    }
    ScriptVO     sVO;        // 脚本信息类
    String       registerID; // 注册类主键
    List<String> regIdList;  // 脚本注册类主键序列
    Object       scriptObj;  // 脚本类实例
    Object       msObj;      // 注册目标类实例
    for (String id : idList) {
      if (okList.contains(id)) {
        continue;
      }
      okList.add(id);
      sVO = scriptMap.get(id);
      if (sVO == null || sVO.importScriptIdList == null || !sVO.importScriptIdList.contains(scriptId)) {
        continue;
      }

      sVO.scriptObj = null; // 清空已经构造的类实例
      scl.remove(sVO); // 清空类缓存
      registerID = getBeanClassInfo(sVO,3);
      if (registerID != null && registerID.length() > 0) {
        try {
          if (sVO.independentClass) {
            scriptObj = bean(getScriptClass(sVO));
          } else {
            scriptObj = getScript(sVO);
          }
          regIdList = BaseUtil.splitToList(registerID, ",");
          for (String rid : regIdList) {
            if (rid == null || rid.length() < 1) {
              continue;
            }
            // 执行重新注册
            if (getBeanFactory().beanExists(rid)) {
              msObj = getBeanFactory().getObject(rid, this);
              ((IBeanRegister) msObj).unRegist(scriptObj);
              ((IBeanRegister) msObj).regist(scriptObj);
            } else if (hasScript(rid)) {
              msObj = getScript(rid); // 注册主类脚本信息类
              if (msObj instanceof ScriptWrapper) {
                // 带扩展包类路径的脚本类，只能通过反射方式注销目标脚本
                ((ScriptWrapper) msObj).runMethod("unRegist", new Class[] { Object.class }, new Object[] { scriptObj });
                ((ScriptWrapper) msObj).runMethod("regist", new Class[] { Object.class }, new Object[] { scriptObj });
              } else {
                ((IBeanRegister) msObj).unRegist(scriptObj);
                ((IBeanRegister) msObj).regist(scriptObj);
              }
            }
          }
        } catch (Exception e) {
          e.printStackTrace();
        }
      }
      refreshForInclude(id, idList, okList, false);
    }
  }

  /**
   * 将脚本信息写入文件
   * 
   * @param sVO 脚本信息类 2016年4月17日
   * @author MBG
   */
  protected void writeSourceFile(ScriptVO sVO) {
    // 建立源文件
    File saveFile = SFilesUtil
        .createFile(SFilesUtil.getAllFilePath(sVO.subPath + "/" + sVO.id + ".xml", getSourceBasePath()));
    // 执行备份
    backupScript(sVO);

    // 写入内容
    FileCopyTools.copyToFile(sVO.toXml(true), "UTF-8", saveFile, false);
    sVO.updateSourceLastChanged(); // 更新新的文件最后修改时间
    scriptMap.put(sVO.id, sVO); // 放入脚本容器中
  }

  /**
   * 保存脚本信息类
   * 
   * @param sVO 脚本信息类
   * @return 保存后的脚本信息类 2014年8月22日
   * @author 马宝刚
   */
  @Override
  public void saveSource(ScriptVO sVO) {
    nativeSaveSource(sVO);
    if (rsl != null) {
      rsl.localChanged(); // 标记脚本发生变化
    }

    for (IModifyScriptTrigger ele : msTriggerList) {
      if (ele.isClusterOnce()) {
        ele.scriptSaved(sVO);
      }
    }
  }

  /**
   * 内部方法，保存脚本信息类，不触发集群脚本同步
   * 
   * @param sVO 脚本信息类
   * @return 保存后的脚本信息类 2014年8月22日
   * @author 马宝刚
   */
  protected void nativeSaveSource(ScriptVO sVO) {
    if (sVO.subPath == null || sVO.subPath.length() < 1) {
      sVO.addErrorMsg("没有找到脚本保存路径");
      return;
    }
    clearScriptRunError(sVO); // 清除之前的错误信息
    if (sVO.independentClass) {
      // 独立的传统类
      saveIndependentSource(sVO);
      return;
    }

    if (sVO.id == null || sVO.id.length() < 1) {
      // 获取文件夹对象
      sVO.id = getScriptNoManager().create(getScriptHeader(sVO.subPath)) + getScriptFooter(sVO.subPath)
          + _beanFactory.getProjectPin();
      if (sVO.id == null || sVO.id.length() < 1) {
        sVO.addErrorMsg("没有获取到新的脚本代码，请检查能否连通远程脚本代码分配服务，或者出现了其它异常");
        return;
      }
      sVO.className = ScriptVO.SCRIPT_CLASS_HEAD + sVO.id;
      sVO.classPath = IScriptLoader.SCRIPT_PACKAGE + "." + sVO.className;
      sVO.sourceFileStatus = 1;
      sVO.uCount = "1";
    } else {
      ScriptVO oScriptVO = scriptMap.get(sVO.id);
      if (oScriptVO != null) {
        if (oScriptVO.subPath != null) {
          // 之前遇到了两个相对路径，实际上是一样的，但其中一个末尾多了个 / 导致被认成了
          // 不在同一个路径中
          String path1 = BaseUtil.trim(oScriptVO.subPath, "/"); // 已有脚本
          String path2 = BaseUtil.trim(sVO.subPath, "/"); // 正在保存的脚本
          if (!path1.equals(path2)) {
            // 保存的脚本与现有脚本重复了
            sVO.addErrorMsg(
                "保存的脚本[" + sVO.id + "]与已有脚本重复 当前脚本路径：[" + sVO.subPath + "] 已有脚本路径：[" + oScriptVO.subPath + "]");
            return;
          }
        }
        sVO.uCount = oScriptVO.uCount;
        if (sVO.modifyContent == null || sVO.modifyContent.length() < 1) {
          sVO.modifyContent = oScriptVO.modifyContent;
        }
      }
      sVO.sourceFileStatus = 2;
    }
    sVO.sourceFilePath = "/" + sVO.subPath + "/" + sVO.id + ".xml";
    sVO.buildSourceFilePath = "/" + sVO.subPath + "/" + sVO.className + ".java";
    sVO.classFilePath = "/" + sVO.subPath + "/" + sVO.className + ".class";

    if (!sVO.isSyncSubmit) {
      sVO.sourceFileVer = getScriptNoManager().newVer();
      if (sVO.uCount == null || sVO.uCount.length() < 1) {
        sVO.uCount = "1";
      } else {
        sVO.uCount = String.valueOf(sint(sVO.uCount) + 1);
      }
    }
    if (sVO.inFields == null) {
      sVO.inFields = new ArrayList<ScriptFieldVO>();
    } else {
      int index = 0; // 序列索引
      while (index < sVO.inFields.size()) {
        if (sVO.inFields.get(index).isStaticPara) {
          sVO.inFields.remove(index);
        } else {
          index++;
        }
      }
    }
    sVO.inFields.addAll(staticFieldList);

    if (sVO.noSingleCompile) {
      // 只针对本次保存，不进行编译
      sVO.noSingleCompile = false;
    } else {
      // 在保存了脚本之后，需要重新通过父类判断该脚本是否为动作脚本
      /*
       * 注意：不能在这里通过新的脚本信息，因为有的脚本在初始化之前还要执行注销方法 ，重新获取新的父类信息，（比如之前是服务类，后来改成了动作类，就容易出问题
       */
      // fixSuperClassInfo(sVO);
      try {
        initScript(sVO, 1); // 初始化脚本
      } catch (RuntimeException e) {
        e.printStackTrace();
        sVO.addErrorMsg(DebugUtil.getExceptionInfo(e, "\n"));
      } catch (Exception e) {
        e.printStackTrace();
        sVO.addErrorMsg(DebugUtil.getExceptionInfo(e, "\n"));
      }
      if (sVO.hasError()) {
        if (sVO.sourceFileStatus == 1) {
          // 删除编译失败的脚本信息
          ScriptUtil.deleteScript(SFilesUtil.getAllFilePath(sVO.subPath + "/" + sVO.className, getClassBasePath()),
              true);
          if (sVO.sourceFileStatus == 1) {
            // 出错以后取消新的主键
            getScriptNoManager().delete(getScriptIdWithoutSuffix(sVO.id));
            sVO.id = null;
          }
        } else if (sVO.sourceFileStatus == 2) {
          // 更新文件失败，准备还原上次正确的

          // 上次正确的脚本容器
          ScriptVO lastVO = scriptMap.get(sVO.id);
          if (lastVO != null) {
            // 肯定不为空
            initScript(lastVO, 1);
          }
        }
        // 出错了
        return;
      }
    }
    writeSourceFile(sVO); // 写入文件
    scriptMap.put(sVO.id, sVO); // 放入脚本容器中

    for (IModifyScriptTrigger ele : msTriggerList) {
      if (!ele.isClusterOnce()) {
        ele.scriptSaved(sVO);
      }
    }
  }

  /**
   * 去掉脚本主键后缀
   * 
   * @param id 脚本主键
   * @return 去掉后缀的脚本主键 2018年1月25日
   * @author MBG
   */
  private String getScriptIdWithoutSuffix(String id) {
    if (id == null || id.length() < 1) {
      return "";
    }
    if (Character.isDigit(id.charAt(id.length() - 1))) {
      return id;
    }
    return getScriptIdWithoutSuffix(id.substring(0, id.length() - 1));
  }

  /**
   * 执行备份脚本
   * 
   * @param scriptID 脚本主键 2015年5月19日
   * @author 马宝刚
   */
  @Override
  public void backupScript(String scriptID) {
    // 获取脚本信息类
    backupScript(scriptMap.get(scriptID));
  }

  /**
   * 执行备份脚本
   * 
   * @param sVO 脚本信息类 2015年5月19日
   * @author 马宝刚
   */
  private void backupScript(ScriptVO sVO) {
    if (sVO == null) {
      return;
    }
    // 脚本文件全路径
    String sourceFileName;
    if (sVO.independentClass) {
      sourceFileName = sVO.id;
    } else {
      sourceFileName = sVO.id;
    }
    String sourcePath = SFilesUtil.getAllFilePath(sVO.subPath + "/" + sourceFileName + ".xml", getSourceBasePath());
    if (!(new File(sourcePath)).exists()) {
      // 文件没找到
      return;
    }
    SDate now = new SDate(); // 当前时间对象
    // 执行备份
    JarTools.addContentJar(SFilesUtil.getAllFilePath(sVO.subPath + "/" + sourceFileName + ".zip", getBakBasePath()),
        sVO.toXml(true), now.getDateNumberString() + "_" + now.getHourString() + now.getMinuteString()
            + now.getSecondString() + "_" + now.getMillisecondString() + "/" + sourceFileName + ".xml");
  }

  /**
   * 获取指定脚本的备份包
   * 
   * @param scriptID 脚本主键
   * @return 指定脚本的备份包 2014年8月25日
   * @author 马宝刚
   */
  @Override
  public String getScriptBak(String scriptID) {
    // 获取对应的脚本信息类
    ScriptVO sVO = getScriptInfo(scriptID, false);
    if (sVO == null) {
      warning("The ScriptID:[" + scriptID + "] Not Found", null);
      return "";
    }
    return SFilesUtil.getAllFilePath(sVO.subPath + "/" + sVO.id + ".jar", getBakBasePath());
  }

  /**
   * 获取要编译的源文件内容
   * 
   * @param scriptID 脚本主键
   * @param outError 是否输出错误信息
   * @return 要编译的源文件内容 2014年8月15日
   * @author 马宝刚
   */
  @Override
  public String getBuildSourceContent(String scriptID, boolean outError) {
    // 获取对应的脚本信息类
    ScriptVO sVO = getScriptInfo(scriptID, false);
    if (sVO == null) {
      return "The ScriptID:[" + scriptID + "] Not Found";
    }
    // 返回脚本源码
    String resContent = sc.outSource(sVO);
    if (outError && sVO.hasError()) {
      return resContent + "\n\n\n\n" + sVO.errorMsg;
    }
    if (resContent.length() < 1) {
      // 没有编译内容，肯定报错了
      return sVO.errorMsg;
    }
    return resContent;
  }

  /**
   * 通过脚本主键返回对应的脚本标题
   * 
   * @param scriptID 脚本主键
   * @return 脚本标题 2014年8月22日
   * @author 马宝刚
   */
  @Override
  public String getScriptTitle(String scriptID) {
    ScriptVO sVO = getScriptInfo(scriptID, false); // 获取对应的脚本信息
    if (sVO == null) {
      return "";
    }
    return SString.valueOf(sVO.title);
  }

  /**
   * 在Action脚本中调用其它脚本（服务），如果其他脚本的传入参数也存在 于页面提交的参数中，则可以直接从页面提交的参数中提取参数值
   * 注意：并不是把动作上下文传入目标脚本中。
   * 
   * @param invoker    调用者类实例
   * @param scriptID   脚本主键
   * @param ac         动作上下文
   * @param invokeType 调用方式 0普通调用 1调用调用主服务器 2广播调用 99由集群发起、响应外部服务器调用
   * @return 脚本返回值
   * @throws Exception 异常 2016年3月21日
   * @author 马宝刚
   */
  @Override
  public <T> T invokeScript(Object invoker, String scriptID, IActionContext ac, int invokeType) throws Exception {
    return invokeScript(invoker, scriptID, ac, invokeType, false);
  }

  /**
   * 执行远程脚本上传文件 该方法不存在调用本地脚本（本地在后台上传毛，文件就在后台）
   * 
   * @param invoker        调用者类实例
   * @param scriptID       目标脚本主键
   * @param params         传入参数 key1=val1&key2=val2
   * @param uploadNameList 上传文件对象名（目标脚本参数名）序列
   * @param fileNameList   源文件名序列
   * @param filePathList   需要上传的文件绝对路径序列
   * @return 调用返回字符串
   * @throws Exception 异常 2019年12月10日
   * @author MBG
   */
  public String remoteScriptUpload(Object invoker, String scriptID, String params, List<String> uploadNameList,
      List<String> fileNameList, List<String> filePathList) throws Exception {
    if (rsl == null) {
      warning("!!!!!!-----The Remote Script Loader[rsl] is NULL");
      return "";
    }
    // 尝试获取该脚本所在的集群分组名
    String groupId = rsl.serverGroup(scriptID);
    if (groupId == null || groupId.length() < 1) {
      if (scriptMap.containsKey(scriptID)) {
        throw new Exception("Can't Execute Upload Script In Local Server");
      }
      // 不存在远程脚本
      throw new Exception("Not Find The Script From Remote Server Cluster");
    }
    return cluster.routeUpload(groupId, scriptID, params, uploadNameList, fileNameList, filePathList, invoker);
  }

  /**
   * 在Action脚本中调用其它脚本（服务），如果其他脚本的传入参数也存在 于页面提交的参数中，则可以直接从页面提交的参数中提取参数值
   * 注意：并不是把动作上下文传入目标脚本中。
   * 
   * @param invoker       调用者类实例
   * @param scriptID      脚本主键
   * @param ac            动作上下文
   * @param invokeType    调用方式 0普通调用 1调用调用主服务器 2广播调用 99由集群发起、响应外部服务器调用
   * @param noReturnValue （用于远程方法调用）如果远程接口没有返回值，在这里标记无需返回值，这样可以提高处理效率
   * @return 脚本返回值
   * @throws Exception 异常 2016年3月21日
   * @author 马宝刚
   */
  @Override
  @SuppressWarnings("unchecked")
  public <T> T invokeScript(Object invoker, String scriptID, IActionContext ac, int invokeType, boolean noReturnValue)
      throws Exception {
    if (!scriptMap.containsKey(scriptID) && rsl != null && rsl.enabled() && invokeType != 99) {
      // 尝试获取该脚本所在的集群分组名
      String groupId = rsl.serverGroup(scriptID);
      if (groupId != null && groupId.length() > 0) {
        // 存在远程脚本
        return (T) cluster.routeCall(groupId, scriptID, ac.getSingleParameterMap(), invokeType, invoker, noReturnValue);
      }
    }
    // 获取线程会话
    ThreadSession.put(IServletConst.KEY_ACTION_CONTEXT, ac); // 放入动作上下文
    String invokerName = getInvokerName(invoker); // 获取调用者名字
    long beforeTime = System.currentTimeMillis(); // 记录执行开始时间
    ScriptVO sVO = getScriptInfo(scriptID, false); // 获取对应的脚本信息
    if (sVO == null) {
      warning("Script:[" + scriptID + "]Not Found.Invoker:[" + invokerName + "]");
      return null;
    }
    if (sVO.disabled) {
      warning("Script:[" + scriptID + "]Has Disabled.Invoker:[" + invokerName + "]");
      return null;
    }
    boolean noLog = false; // 是否准备运行脚本前，禁用日志输出
    if (!boo(ThreadSession.get("_nolog_")) && sVO.noOutLog) {
      // 如果当前允许输出日志并且当前要运行的脚本禁止输出日志
      noLog = true;
      outLog(false);
    }
    info("Begin Invoke Script:[" + scriptID + "]Invoker:[" + invokerName + "]");
    // 拼装传入参数
    Map<String, Object> paraMap = new HashMap<String, Object>();
    for (ScriptFieldVO sfVO : sVO.inFields) {
      if (sfVO.needB64Dec) {
        // 妈的，是解码，不是编码，当时脑子在想什么
        // paraMap.put(sfVO.name,Base64.base64Encode(ac.getParameter(sfVO.name),"UTF-8"));
        paraMap.put(sfVO.name, d64(ac.getParameter(sfVO.name)));
      } else {
        paraMap.put(sfVO.name, ac.getParameter(sfVO.name));
      }
    }
    // 注意，凡是集群管理类调用的，都不进入集群处理，避免死循环
    if (invokeType != 0 && cluster != null && cluster.clusterMode() && !cluster.equals(invoker)) {
      // 可以由服务调用集群中的动作

      // 集群模式 （如果当前服务器是主服务器，就不能用集群调用，否则会死循环）
      if (invokeType == 1 && !cluster.master()) {
        // 调用主服务器动作
        return (T) cluster.call(sVO.id, ac, 1);
      } else if (invokeType == 2) {
        // 广播集群中所有服务器动作

        // 执行广播，但是不广播到自己
        cluster.callAll(sVO.id, ac);

        // 不返回，执行本机动作
        // return null;
      }
    }
    return invokeScript(sVO, paraMap, invoker, invokerName, invokeType, ac, noLog, beforeTime);
  }

  /**
   * 调用指定的脚本
   * 
   * @param invoker  调用者类实例
   * @param scriptID 脚本主键
   * @return 脚本返回值
   * @throws Exception 异常 2014年7月29日
   * @author 马宝刚
   */
  @Override
  public <T> T invokeScript(Object invoker, String scriptID) throws Exception {
    return invokeScript(invoker, scriptID, 0);
  }

  /**
   * 调用指定的脚本
   * 
   * @param invoker    调用者类实例
   * @param scriptID   脚本主键
   * @param invokeType 调用方式 0普通调用 1调用调用主服务器 2广播调用 99由集群发起，响应外部服务器调用
   * @return 脚本返回值
   * @throws Exception 异常 2014年7月29日
   * @author 马宝刚
   */
  @Override
  public <T> T invokeScript(Object invoker, String scriptID, int invokeType) throws Exception {
    return invokeScript(invoker, scriptID, invokeType, false);
  }

  /**
   * 调用指定的脚本
   * 
   * @param invoker        调用者类实例
   * @param scriptID       脚本主键
   * @param invokeType     调用方式 0普通调用 1调用调用主服务器 2广播调用 99由集群发起，响应外部服务器调用
   * @param noReturenValue （用于远程方法调用）如果远程接口没有返回值，在这里标记无需返回值，这样可以提高处理效率
   * @return 脚本返回值
   * @throws Exception 异常 2014年7月29日
   * @author 马宝刚
   */
  @Override
  @SuppressWarnings("unchecked")
  public <T> T invokeScript(Object invoker, String scriptID, int invokeType, boolean noReturenValue) throws Exception {
    if (!scriptMap.containsKey(scriptID) && rsl != null && rsl.enabled() && invokeType != 99) {
      // 尝试获取该脚本所在的集群分组名
      String groupId = rsl.serverGroup(scriptID);
      if (groupId != null && groupId.length() > 0) {
        // 存在远程脚本
        return (T) cluster.routeCall(groupId, scriptID, null, invokeType, invoker, noReturenValue);
      }
    }
    String invokerName = getInvokerName(invoker); // 获取调用者名字
    long beforeTime = System.currentTimeMillis(); // 记录执行开始时间
    ScriptVO sVO = getScriptInfo(scriptID, false); // 获取对应的脚本信息
    if (sVO == null) {
      warning("Script:[" + scriptID + "]Not Found.Invoker:[" + invokerName + "] ");
      return null;
    }
    if (sVO.disabled) {
      warning("Script:[" + scriptID + "]Has Disabled.Invoker:[" + invokerName + "] ");
      return null;
    }
    boolean noLog = false; // 是否准备运行脚本前，禁用日志输出
    if (!boo(ThreadSession.get("_nolog_")) && sVO.noOutLog) {
      // 如果当前允许输出日志并且当前要运行的脚本禁止输出日志
      noLog = true;
      outLog(false);
    }
    info("Begin Invoke Script:[" + scriptID + "]Invoker:[" + invokerName + "]");
    // 注意，凡是集群管理类调用的，都不进入集群处理，避免死循环
    if (invokeType != 0 && cluster != null && cluster.clusterMode() && !cluster.equals(invoker)) {
      // 可以由服务调用集群中的动作
      // 集群模式 （如果当前服务器是主服务器，就不能用集群调用，否则会死循环）
      if (invokeType == 1 && !cluster.master()) {
        // 调用主服务器动作
        return (T) cluster.call(sVO.id, 1);
      } else if (invokeType == 2) {
        // 广播集群中所有服务器动作
        // 执行广播，但是不广播到自己
        cluster.callAll(sVO.id, false);
        // 不返回，执行本机动作
        // return null;
      }
    }
    // 执行调用脚本
    return invokeScript(sVO, new HashMap<String, Object>(), invoker, invokerName, invokeType, null, noLog, beforeTime);
  }

  /**
   * 调用脚本类 【内部方法】
   * 
   * @param sVO         脚本信息类
   * @param scriptObj   脚本类实例
   * @param paraMap     传入参数
   * @param invoker     调用者
   * @param invokerName 调用者类名称
   * @param noLog       是否不显示日志
   * @param beforeTime  运行起始时间
   * @return 调用结果
   * @throws Exception 异常 2019年1月22日
   * @author MBG
   */
  @SuppressWarnings({ "unchecked", "rawtypes" })
  private <T> T invokeScript(ScriptVO sVO, Map paraMap, Object invoker, String invokerName, int invokeType,
      IActionContext ac, boolean noLog, long beforeTime) throws Exception {
    Object res; // 构建返回值
    Object scriptObj = getScript(sVO); // 获取脚本类
    if (scriptObj == null) {
      if (noLog) {
        outLog(true);
      }
      throw new MsgException(this, "The Script:[" + sVO.id + "] Has Not Found Invoker:[" + invokerName + "]");
    }
    if ((scriptObj instanceof IActionBean)) {
      res = invokeActionScript((IActionBean) scriptObj, invoker, paraMap, invokeType == 99, ac);
      if (noLog) {
        outLog(true);
      }
      info("( ^3^ )╱~~  ScriptLoader Invoke Script Completed[" + sVO.id + "]Invoker:[" + invokerName + "]UseTime["
          + (System.currentTimeMillis() - beforeTime) + "]ms");
      return (T) res;
    }
    // 检查并设置默认值
    if (paraMap != null) {
      Object val; // 参数值元素
      for (ScriptFieldVO ele : sVO.inFields) {
        if (ele.defValue != null && ele.defValue.length() > 0) {
          val = paraMap.get(ele.name);
          if (val == null) {
            if (ele.needB64Dec) {
              // 应该是解码，不是编码
              // paraMap.put(ele.name,Base64.base64Encode(ele.defValue,"UTF-8"));
              paraMap.put(ele.name, d64(ele.defValue));
            } else {
              paraMap.put(ele.name, ele.defValue);
            }
          }
        }
      }
    }
    ThreadSession.put("_invoker_", invokerName); // 将调用者名字放入线程会话中
    try {
      if (scriptObj != null && (scriptObj instanceof IScriptInvokeEvent)) {
        // 该脚本实现了脚本运行事件接口
        res = ((IScriptInvokeEvent) scriptObj).beforeInvoke(invoker, null);
        if (res == null) {
          if (sVO.outType == null || sVO.outType.length() < 1) {
            if (scriptObj instanceof IScriptTrusteeship) {
              ((IScriptTrusteeship) scriptObj)._executeXN(paraMap);
            } else {
              ((IScriptBean) scriptObj)._executeN(paraMap);
            }
          } else {
            if (scriptObj instanceof IScriptTrusteeship) {
              res = ((IScriptTrusteeship) scriptObj)._executeX(paraMap);
            } else {
              res = ((IScriptBean) scriptObj)._execute(paraMap);
            }
          }
          ((IScriptInvokeEvent) scriptObj).afterInvoke(invoker, paraMap, res);
        }
      } else {
        if (sVO.outType == null || sVO.outType.length() < 1) {
          res = null;
          if (scriptObj instanceof IScriptTrusteeship) {
            ((IScriptTrusteeship) scriptObj)._executeXN(paraMap);
          } else {
            ((IScriptBean) scriptObj)._executeN(paraMap);
          }
        } else {
          if (scriptObj instanceof IScriptTrusteeship) {
            res = ((IScriptTrusteeship) scriptObj)._executeX(paraMap);
          } else {
            res = ((IScriptBean) scriptObj)._execute(paraMap);
          }
        }
      }
      if (noLog) {
        outLog(true);
      }
      info("( ^3^ )╱~~  ScriptLoader Invoke Script Completed:[" + sVO.id + "]Invoker:[" + invokerName + "]UseTime:["
          + (System.currentTimeMillis() - beforeTime) + "]ms");
      return (T) res;
    } catch (Exception e) {
      if (noLog) {
        outLog(true);
      }
      e.printStackTrace();
      error("InvokeScript:[" + sVO.id + "] Invoker:[" + invokerName + "] Exception", e);
      throw e;
    }
  }

  /**
   * 批量编译指定路径下的全部脚本 通常用在保存了一批暂不编译的脚本
   * 
   * @param subPath 指定路径
   * @return 返回编译报错信息 2016年4月14日
   * @author MBG
   */
  @Override
  public String compileScriptByPath(String subPath) {
    subPath = BaseUtil.swapString(subPath, "\\", "/");
    if (!subPath.startsWith("/")) {
      subPath = "/" + subPath;
    }
    if (subPath.endsWith(".xml")) {
      subPath = subPath.substring(0, subPath.lastIndexOf("/"));
    }
    // 脚本文件序列
    List<String> pathList = new ArrayList<String>();

    try {
      // 执行搜索
      SFilesUtil.getFileList(pathList, SFilesUtil.getAllFilePath(subPath, getSourceBasePath()), null, "xml", true,
          true);
    } catch (Exception e) {
      e.printStackTrace();
    }
    // 排序容器
    SortVector<ScriptVO> sv = new SortVector<ScriptVO>();
    ScriptVO sVO; // 脚本信息容器
    String scriptId; // 脚本主键
    for (String path : pathList) {
      if (path.endsWith(IScriptLoader.SCRIPT_FOLDER_INFO_NAME)) {
        // 不处理文件夹说明配置文件
        continue;
      }
      scriptId = filesUtil.getFileBeforeName(path);
      sVO = scriptMap.get(scriptId);
      if (sVO == null) {
        warning("Not Find The Script:[" + scriptId + "] When Compile Script");
        continue;
      }

      sv.add(sVO, sVO.singletonLevel);
    }
    // 返回报错信息
    StringBuffer reSbf = new StringBuffer();
    sv.desc();
    while (sv.hasNext()) {
      sVO = sv.nextValue();
      initScript(sVO, 2); // 编译初始化

      if (sVO.errorMsg != null && sVO.errorMsg.length() > 0) {
        reSbf.append(sVO.id).append(":\n").append(sVO.errorMsg).append("\n\n");
      }

    }
    return reSbf.toString();
  }

  /**
   * 显示当前路径详细信息
   * 
   * @param subPath 指定路径
   * @return 当前路径相信信息 2014年8月7日
   * @author 马宝刚
   */
  @Override
  public FileVO getPathInfo(String subPath) {
    // 先检查内置文件
    String jarSubPath = "/script_src" + subPath + "/" + SCRIPT_FOLDER_INFO_NAME;
    if (rl.hasFile(jarSubPath)) {
      try {
        return (new FileVO()).parse(rl.getFile(jarSubPath), subPath, false);
      } catch (Exception e) {
        e.printStackTrace();
        return new FileVO();
      }
    }
    return (new FileVO()).parse(subPath, getSourceBasePath(), false);
  }

  /**
   * 获取全部路径和文件信息
   * 
   * @return 路径和文件信息序列 2017年2月23日
   * @author MBG
   */
  @Override
  public List<FileVO> getAllInfoList() {
    // 构建返回值
    List<FileVO> reList = new ArrayList<FileVO>();
    // 搜索根路径
    setSubInfoList("", reList);
    return reList;
  }

  /**
   * 递归设置路径和文件信息
   * 
   * @param subPath 路径
   * @param allList 全部信息序列 2017年2月23日
   * @author MBG
   */
  private void setSubInfoList(String subPath, List<FileVO> allList) {
    // 获取根路径下的信息
    List<FileVO> subList = getPathList(subPath);
    for (FileVO ele : subList) {
      allList.add(ele);
      if (ele.isPath) {
        setSubInfoList(ele.subPath, allList);
      }
    }
  }

  /**
   * 获取指定路径下的子路径和文件信息序列
   * 
   * @param subPath 指定路径
   * @return 子路径和信息序列 2014年8月5日
   * @author 马宝刚
   */
  @Override
  public List<FileVO> getPathList(String subPath) {
    subPath = BaseUtil.swapString(subPath, "\\", "/");
    if (subPath.length() > 0 && !subPath.startsWith("/")) {
      subPath = "/" + subPath;
    }
    // 根路径
    String basePath = getSourceBasePath() + subPath;
    // 构建返回值
    List<FileVO> reList = new ArrayList<FileVO>();

    // 返回值中的文件序列
    List<FileVO> fileList = new ArrayList<FileVO>();

    // 如果外部路径中存在资源，就不用显示同样路径的内部资源信息
    List<String> pathList = new ArrayList<String>();

    // 文件目录列表序列
    SortVector<String> pathSv = new SortVector<String>();

    // 执行搜索当前文件夹
    try {
      //// 如果是开发模式，则允许使用.disabled文件屏蔽指定文件夹中的脚本
      SFilesUtil.getFileList(pathSv, basePath, null, "xml", true, _beanFactory.isDevelopMode());
    } catch (Exception e) {
      e.printStackTrace();
    }
    pathSv.asc(); // 按照由小到大排序

    FileVO fVO; // 文件信息类
    File cFile; // 检测文件对象
    String path; // 路径元素
    boolean disabledPath; // 是否禁用该文件夹
    while (pathSv.hasNext()) {
      path = pathSv.nextValue();

      disabledPath = false;
      // 检测是否屏蔽掉该文件夹
      for (String disPath : disabledScriptPathList) {
        if (path.startsWith(disPath)) {
          disabledPath = true;
          break;
        }
      }
      if (disabledPath) {
        continue;
      }

      path = "/" + path;
      try {
        cFile = SFilesUtil.getFileByName(path, basePath);
      } catch (Exception e) {
        e.printStackTrace();
        continue;
      }
      if (!cFile.exists()) {
        continue;
      }
      if (cFile.isDirectory()) {
        // 文件夹
        try {
          cFile = SFilesUtil.getFileByName(path + "/" + SCRIPT_FOLDER_INFO_NAME, basePath);
        } catch (Exception e) {
          // 没找到该文件夹中默认信息文件
          cFile = null;
        }
        pathList.add(subPath + path);
        if (cFile == null || !cFile.exists()) {
          fVO = new FileVO();
          fVO.isPath = true;
          fVO.subPath = subPath + path;
          fVO.aname = "[注意：该分类的说明文件" + SCRIPT_FOLDER_INFO_NAME + "丢失]";
          fVO.explain = "[注意：该分类的说明文件" + SCRIPT_FOLDER_INFO_NAME + "丢失]";
          reList.add(fVO);
        } else {
          fVO = new FileVO();
          fVO.isPath = true;
          try {
            fVO.parse(cFile, subPath + path, basePath, false);
            if (fVO.hidden) {
              // 不解析隐藏文件夹
              continue;
            }
            reList.add(fVO);
          } catch (Exception e) {
            e.printStackTrace();
            continue;
          }
        }
      } else {
        // 源文件
        if (path.endsWith(SCRIPT_FOLDER_INFO_NAME)) {
          // 不显示当前路径配置信息
          continue;
        }
        fVO = new FileVO();
        fVO.id = filesUtil.getFileBeforeName(path);
        fVO.isPath = false;
        try {
          fVO.parse(cFile, subPath + path, basePath, true);
          if (fVO.hidden) {
            // 不解析隐藏文件夹
            continue;
          }
          fileList.add(fVO);
        } catch (Exception e) {
          e.printStackTrace();
          continue;
        }
      }
    }

    // 放入内置的脚本等信息
    String jarBasePath = "/script_src" + subPath;
    String jarSubPath; // 压缩文件中的虚拟路径
    String jarCheckPath; // 用于判断是否为当前目录
    List<String> jarPathList = rl.getSubPathList(jarBasePath);
    for (String ele : jarPathList) {
      jarSubPath = ele.substring(11);
      jarCheckPath = jarSubPath.substring(subPath.length());
      if (jarCheckPath.endsWith(SCRIPT_FOLDER_INFO_NAME)) {
        // 文件夹
        if (jarCheckPath.indexOf("/") < 0
            || jarCheckPath.substring(0, jarCheckPath.lastIndexOf("/")).indexOf("/") > -1) {
          // 这是其子文件夹内部文件夹中的说明文件，并不是当前子文件夹的说明文件
          continue;
        }
        fVO = new FileVO();
        fVO.isNative = true;
        fVO.isPath = true;
        try {
          fVO.parse(rl.getFile(ele), jarSubPath, false);
          if (fVO.hidden) {
            // 不解析隐藏文件夹
            continue;
          }
          reList.add(fVO);
        } catch (Exception e) {
          e.printStackTrace();
        }
      } else {
        // 脚本文件
        if (jarSubPath.substring(subPath.length()).indexOf("/") > -1) {
          // 这不是当前文件夹中的脚本
          continue;
        }
        fVO = new FileVO();
        fVO.id = filesUtil.getFileBeforeName(jarSubPath);
        fVO.isPath = false;
        fVO.isNative = true;
        try {
          fVO.parse(rl.getFile(ele), jarSubPath, true);
          if (fVO.hidden) {
            // 不解析隐藏文件夹
            continue;
          }
          fileList.add(fVO);
        } catch (Exception e) {
          e.printStackTrace();
          continue;
        }
      }
    }
    reList.addAll(fileList);
    return reList;
  }

  /**
   * 删除所有编译文件 2014年8月11日
   * 
   * @author 马宝刚
   */
  protected void deleteAllBuildFile() {
    try {
      filesUtil.deletePath(getClassBasePath());
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  /**
   * 删除指定脚本
   * 
   * @param scriptID 脚本信息主键 2014年8月11日
   * @author 马宝刚
   */
  @Override
  public void deleteScript(String scriptID) {
    deleteScript(scriptID, null);
  }

  /**
   * 删除指定脚本
   * 
   * @param scriptID 脚本信息主键
   * @param message  删除原因说明 2019年10月30日
   * @author MBG
   */
  @Override
  public void deleteScript(String scriptID, String message) {
    // 获取指定的脚本信息类
    ScriptVO sVO = getScriptInfo(scriptID, false);
    if (sVO == null) {
      return;
    }
    if (message != null) {
      // 设置删除原因信息
      sVO.modifyContent = message;
    }
    nativeDeleteScript(sVO); // 执行删除
    if (rsl != null) {
      rsl.deletedChange(scriptID, message); // 标记脚本发生变化
    }
    for (IModifyScriptTrigger ele : msTriggerList) {
      if (ele.isClusterOnce()) {
        ele.scriptDeleted(sVO);
      }
    }
  }

  /**
   * 内部方法，删除指定脚本，不触发同步集群功能
   * 
   * @param scriptID 脚本主键
   * @param message  删除原因 2019年10月19日
   * @author MBG
   */
  protected void nativeDeleteScript(ScriptVO sVO) {
    if (sVO == null) {
      return;
    }
    backupScript(sVO); // 先执行备份

    deleteBuildScript(sVO); // 删除构建文件

    /**
     * 删除源文件
     */
    if ((new File(getSourceBasePath() + sVO.sourceFilePath)).isFile()) {
      try {
        SFilesUtil.deletePath(getSourceBasePath() + sVO.sourceFilePath);
      } catch (Exception e) {
        e.printStackTrace();
      }
    }

    // 删除脚本编译资源
    fm.delete(sVO.id);

    // 从脚本容器中移除
    scriptMap.remove(sVO.id);

    // 如果删除一个以前在用的脚本，这个编号被新的脚本使用，
    // 就会出现意想不到的问题，所以注释调，永久作废该编号
    // deleteScriptId(scriptID); //销毁该编号

    for (IModifyScriptTrigger ele : msTriggerList) {
      if (!ele.isClusterOnce()) {
        ele.scriptDeleted(sVO);
      }
    }
  }

  /**
   * 重新编译指定脚本
   * 
   * @param scriptID 脚本信息主键，或者为传统类名
   * @param subPath  相对路径 2014年8月11日
   * @author 马宝刚
   */
  @Override
  public void reBuildScript(String scriptId, String subPath) {
    // 获取指定脚本信息类
    ScriptVO sVO = scriptMap.get(scriptId);
    if (sVO == null) {
      sVO = new ScriptVO(this);
      sVO.sl = this;
      sVO.subPath = subPath;
      sVO.id = scriptId;
      sVO.sourceFilePath = "/" + subPath + "/" + scriptId + ".xml";
      SFilesUtil.createFile(getClassBasePath() + "/" + sVO.subPath + "/"); // 如果路径不存在，则自动建立该路径
      getScriptNoManager().setMaxid(scriptId); // 尝试设置最大主键值
      // 加载脚本信息
      if (loadSource(sVO)) {
        sVO.classFilePath = "/" + subPath + "/" + sVO.className + ".class";
        sVO.buildSourceFilePath = "/" + subPath + "/" + sVO.className + ".java";
        initScript(sVO, 1); // 初始化脚本
        scriptMap.put(scriptId, sVO);
      }
    } else {
      // 因为在initScript方法中，会执行loadSource方法，也执行了destoryScript方法
      // //执行注销方法
      // destoryScript(sVO);
      // if(loadSource(sVO)) {
      // initScript(sVO,2);
      // }
      if (sVO.independentClass) {
        // 如果是传统类，需要比较配置文件与编译后的class中的ver值是否一致，如果一致，则不再重新初始化
        // 之前遇到过这种情况，并行集群服务器中，源码文件夹是共享的，因为所有服务器并不是同时启动，在第二台
        // 启动后，发现传统类脚本文件时间发生了变化，于是重新构造这个传统类脚本，构造以后会将配置信息回写到
        // 配置文件中，导致文件时间发生了变化，被其它服务器检测出来，又重新编译，又回写脚本源文件，导致时间又发生了变化
        // 获取脚本类文件对象
        ClassFile cf = fm.getClassFile(sVO);
        if (cf != null) {
          // 类信息
          String[] clsInfos = ClassUtil.getClassInfo(cf);
          if (clsInfos != null && clsInfos.length > 0 && clsInfos[0].equals(sVO.sourceFileVer)) {
            log("<*>_<*>The Script:[" + sVO.id + "] Has No Changed,Ignore");
            return;
          }
        }
      }
      initScript(sVO, 2);
    }

    // 重新编译脚本不要触发本地脚本发生变化，否则多台同分组服务器共享脚本根路径时，会出现同步风暴，导致服务器锁死
    // 同步风暴：监控线程发现本地脚本发生变化，在重新编译后，广播给通群组其它服务器，因为路径是共享的，脚本已经是最新的了，
    // 结果也广播给其它服务器，在收到广播后，又重新编译，又被监控线程发现文件发生变化
    // 另外在重新编译脚本这块也没必要标记脚本发生变化。只在保存脚本地方标记
    // rsl.localChanged(); //标记脚本发生变化
  }

  /**
   * 删除构建文件
   * 
   * @param sVO 脚本信息类 2014年8月11日
   * @author 马宝刚
   */
  protected void deleteBuildScript(ScriptVO sVO) {
    // 执行注销方法
    destoryScript(sVO);
    // 从脚本资源中移除（必须在删除文件之前执行）
    fm.delete(sVO);
    // 构建路径
    String buildFile = SFilesUtil.getAllFilePath(sVO.subPath, getClassBasePath()) + "/" + sVO.className;
    ScriptUtil.deleteScript(buildFile, false); // 删除指定脚本类
  }

  /**
   * 重新编译全部源文件 2014年8月11日
   * 
   * @author 马宝刚
   */
  @Override
  public void rebuildAll() {
    // 获取全部
    List<String> scriptIdList = BaseUtil.getMapKeyList(scriptMap);
    ScriptVO sVO; // 脚本元素
    for (String scriptID : scriptIdList) {
      sVO = scriptMap.get(scriptID);
      destoryScript(sVO);
    }
    scriptMap.clear(); // 清空全部脚本信息
    deleteAllBuildFile(); // 删除全部编译好的文件
    searchSource(); // 重新加载全部脚本
    initScripts(false); // 初始化全部脚本
  }

  /**
   * 不用该功能，文件夹的名字过于复杂，不利于口头告诉他人 至于因为文件夹名字很容易重复，容易打错补丁。已经在
   * 文件管理功能中，将简单的文件夹名转义为汉字文件夹名字
   * 
   * 建立新的脚本文件夹名 xxxx-xxxx-xxxx-xxxx
   * 
   * @param index 索引（防止文件夹重名）
   * @return 文件夹名 2015年12月1日
   * @author 马宝刚
   */
  protected String createFolderName_STOP(int index) {
    // 用毫秒值建立文件夹
    String fName = String.valueOf(System.currentTimeMillis()) + index;
    while (fName.length() < 16) {
      fName += "0";
    }
    return "{" + fName.substring(0, 4) + "-" + fName.substring(4, 8) + "-" + fName.substring(8, 12) + "-"
        + fName.substring(12, 16) + "}";
  }

  /**
   * 保存路径信息
   * 
   * @param fileVO 路径信息包
   * @return 保存后的文件全路径 2014年8月5日
   * @author 马宝刚
   */
  @Override
  public String savePathInfo(FileVO fileVO) {
    String subPath = BaseUtil.swapString(fileVO.subPath, "\\", "/");
    if (!subPath.startsWith("/")) {
      subPath = "/" + subPath;
    }
    // 根路径
    String path = getSourceBasePath() + subPath;

    /*
     * 
     * 停止使用复杂文件夹名字，原因写在了 createFolderName 方法中
     * 
     * //是否为新增子文件夹 if(fileVO.isAdd) { int index = 0; //文件夹索引 String folderName =
     * createFolderName(index); //构造文件夹名
     * while(SFilesUtil.isFileExist(path+folderName+"/",null)) { folderName =
     * createFolderName(++index); } path = path+folderName+"/";
     * filesUtil.createFile(path); }
     */

    // 是否为新增子文件夹
    if (fileVO.isAdd) {
      int subName = 0; // 文件夹名
      while (SFilesUtil.isFileExist(path + subName + "/", null)) {
        subName++;
      }
      path = path + subName + "/";
      filesUtil.createFile(path);
    }
    if (fileVO.ver == null || fileVO.ver.length() < 1) {
      fileVO.ver = SDate.nowDateTimeWithoutSecond();
    }
    // 保存文件对象
    File saveFile;
    try {
      saveFile = SFilesUtil.getFileByName(SCRIPT_FOLDER_INFO_NAME, path);
    } catch (Exception e) {
      saveFile = SFilesUtil.createFile(SFilesUtil.getAllFilePath(SCRIPT_FOLDER_INFO_NAME, path));
    }
    // 写入内容
    FileCopyTools.copyToFile(fileVO.toXml(), "UTF-8", saveFile, false);
    return saveFile.getPath();
  }

  /**
   * 判断是否存在该脚本类
   * 
   * @param scriptId 脚本信息主键
   * @return 是否存在该脚本类 2014年8月14日
   * @author 马宝刚
   */
  @Override
  public boolean hasScript(String scriptId) {
    return scriptMap.containsKey(scriptId);
  }

  /**
   * 将指定脚本源文件移到指定路径中
   * 
   * @param scriptID 脚本主键
   * @param subPath  目标相对路径 2014年8月22日
   * @author 马宝刚
   */
  @Override
  public ScriptVO moveSource(String scriptID, String subPath) {
    // 原本是不做克隆，直接修改原信息VO，但是在保存的时候，就会误以为要保存的脚本与已存在的脚本主键重复
    ScriptVO sVO = getScriptInfo(scriptID, true); // 克隆
    if (sVO == null) {
      error("The Script ID:[" + scriptID + "] For Move To [" + subPath + "] Not Found", null);
      return null;
    }
    if (!(new File(SFilesUtil.getAllFilePath(subPath, getSourceBasePath()))).exists()) {
      error("The Script ID:[" + scriptID + "] Move To The Path [" + subPath + "] Not Found", null);
      sVO.addErrorMsg("The Script ID:[" + scriptID + "] Move To The Path [" + subPath + "] Not Found");
      return sVO;
    }
    deleteScript(scriptID); // 删除旧的
    sVO.classFileVer = null; // 清空编译类的版本
    sVO.subPath = subPath; // 设置新路径
    saveSource(sVO); // 保存到目标
    return sVO;
  }

  /**
   * 通过文件夹路径，获取该文件夹对象
   * 
   * @param path 脚本路径
   * @return 文件对象 2015年2月13日
   * @author 马宝刚
   */
  private FileVO getScriptFolder(String path) {
    if (path == null || path.length() < 1 || "/".equals(path)) {
      return null;
    }
    if (!path.startsWith("/")) {
      path = "/" + path;
    } else if (path.indexOf("/") < 0) {
      return null;
    }
    // 文件夹信息
    FileVO fVO = getPathInfo(path);
    if (!fVO.isPath) {
      return null;
    }
    return fVO;
  }

  /**
   * 通过文件夹路径，获取该文件夹中定义的脚本头值
   * 
   * 已修改为：从根路径的文件夹头累加到当前文件夹的头
   * 
   * @param path 脚本路径
   * @return 从根路径的文件夹头累加到当前文件夹的头 2015年2月13日
   * @author 马宝刚
   */
  @Override
  public String getScriptHeader(String path) {
    // 获取文件夹对象
    FileVO fVO = getScriptFolder(path);
    if (fVO == null || !fVO.isPath) {
      return "";
    }
    // 父文件夹
    String resHeader = "";
    if (fVO.upPath != null && fVO.upPath.length() > 0) {
      resHeader = getScriptHeader(fVO.upPath);
    }
    if (fVO.atype != null && fVO.atype.length() > 0) {
      resHeader += fVO.atype;
    }
    return resHeader;
  }

  /**
   * 通过文件夹路径，获取从根路径的文件夹获取到当前该文件夹中定义的脚本后缀
   * 
   * 注意：脚本后缀不继承累加父分类后缀，之后当前分类没有后缀时，才会继承父分类后缀
   * 
   * @param path 脚本路径
   * @return 获取该文件夹中定义的脚本后缀 2015年2月13日
   * @author 马宝刚
   */
  @Override
  public String getScriptFooter(String path) {
    // 获取文件夹对象
    FileVO fVO = getScriptFolder(path);
    if (fVO == null || !fVO.isPath) {
      return "";
    }
    if (fVO.footer != null && fVO.footer.length() > 0) {
      return fVO.footer;
    }
    if (fVO.upPath != null && fVO.upPath.length() > 0) {
      return getScriptFooter(fVO.upPath);
    }
    return "";
  }

  /**
   * 建立全部脚本的备份文件
   * 
   * @return 备份文件路径 2014年8月25日
   * @author 马宝刚
   */
  @Override
  public String createAllSourceBak() {
    // 全部脚本备份文件根路径
    String filePath = getBakBasePath() + "/_all/";
    int fileIndex = 1; // 备份文件序号
    String dateNumber = (new SDate()).getDateNumberString(); // 文件名段（日期）
    String jarFilePath = filePath + "all_" + dateNumber + "_" + fileIndex + ".zip"; // 备份的文件路径
    // 备份文件
    File bakFile = new File(jarFilePath);
    while (bakFile.exists()) {
      fileIndex++;
      jarFilePath = filePath + "all_" + dateNumber + "_" + fileIndex + ".zip";
      bakFile = new File(jarFilePath);
    }
    info("--------Begin Create Backup Source Script File...");
    // 搜索到的文件序列
    List<String> fileList = new ArrayList<String>();
    try {
      SFilesUtil.getFileList(fileList, getSourceBasePath(), null, "xml", true, true);
    } catch (Exception e) {
      e.printStackTrace();
      error("Scan Source Script File Error,SourceBasePath:[" + getSourceBasePath() + "]", e);
      return "";
    }
    info("--------Search File Count:[" + fileList.size() + "] Begin Do Backup...");

    // 构建打包类
    JarTools jt = new JarTools();
    // 建立打包文件
    Object jarObj = jt.createJarFile(jarFilePath);
    try {
      for (String eleFile : fileList) {
        jt.addJarFileElement(jarObj, filesUtil.getFileInputStreamByUrl(getSourceBasePath() + "/" + eleFile), eleFile);
      }
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      jt.closeJarFile(jarObj, null);
    }
    return jarFilePath;
  }

  /**
   * 覆盖方法 刘虻 2015年3月31日
   */
  @Override
  public boolean regist(Object bean) {

    // 放入脚本保存删除触发事件处理类序列
    if (bean instanceof IModifyScriptTrigger && !msTriggerList.contains(bean)) {
      msTriggerList.add((IModifyScriptTrigger) bean);
    }

    if(bean instanceof IFilter) {
      if (bean instanceof IBeanRegisterChild) {
        ((IBeanRegisterChild) bean).setRegister(this);
        ((IBeanRegisterChild) bean).afterRegister();
      }
    }else if (bean instanceof IScriptBean) {
      // 通过配置文件注入的类实例
      ScriptVO sVO = new ScriptVO();
      // 类文件对象
      ClassFile cf = fm.getClassFile(bean.getClass().getName());
      // 设置脚本配置信息
      sVO.setClassInfo(ClassUtil.getScriptInfo(cf));
      // 设置脚本扩展库路径信息
      sVO.setExtLibs(ClassUtil.getExtLib(cf));
      if (scriptMap.containsKey(sVO.id)) {
        return false;
      }
      scriptMap.put(sVO.id, sVO);
    }
    // 不能用注册功能将脚本代码管理类脚本注册进来
    // 因为这个时候脚本管理类还没初始化完毕，
    // 就造成了锁死
    // if(bean instanceof IScriptNoManager) {
    // snm = (IScriptNoManager)bean;
    // }
    return true;
  }

  /**
   * 设置脚本编号管理器
   * 
   * @param snm 脚本编号管理器 2016年4月7日
   * @author 马宝刚
   */
  @Override
  public void setScriptNoManager(IScriptNoManager snm) {
    if (snm == null) {
      return;
    }
    this.snm = snm;
  }

  /**
   * 获取脚本编号管理器
   * 
   * @return 脚本编号管理器 2017年1月12日
   * @author MBG
   */
  @Override
  public IScriptNoManager getScriptNoManager() {
    if (snm == null) {
      snm = new NativeScriptNoManager();
    }
    return snm;
  }

  /**
   * 获取重复的脚本信息
   * 
   * @return 重复的脚本信息 2015年5月15日
   * @author 马宝刚
   */
  @Override
  public List<String> getRepeatScriptInfo() {
    List<String> scriptIdList = BaseUtil.getMapKeyList(repeatScriptMap);
    // 构建返回值
    List<String> reList = new ArrayList<String>();
    List<String> infoList; // 信息元素序列
    StringBuffer sbf; // 输出信息
    for (String scriptId : scriptIdList) {
      infoList = repeatScriptMap.get(scriptId);
      if (infoList != null && infoList.size() > 1) {
        sbf = new StringBuffer();
        sbf.append("[").append(scriptId).append("]");
        for (String subPath : infoList) {
          sbf.append(" [").append(subPath + "]");
        }
        reList.add(sbf.toString());
      }
    }
    return reList;
  }

  /**
   * 是否没有脚本源码，只有编译后的脚本类
   * 
   * @return true没有脚本源码 2015年5月19日
   * @author 马宝刚
   */
  @Override
  public boolean isClassOnly() {
	  return classOnly;
  }
  
  /**
   * 设置是否编译后保留脚本类的Java文件
   * @param deleteBuildSource 是否编译后保留脚本类的Java文件
   * 2021年9月11日
   * @author MBG
   */
  public void setDeleteBuildSource(boolean deleteBuildSource) {
	  this.deleteBuildSource = deleteBuildSource;
  }

  /**
   * 获取全部脚本主键序列
   * 
   * @return 全部脚本主键序列 2015年5月19日
   * @author 马宝刚
   */
  @Override
  public List<String> getScriptIdList() {
    return BaseUtil.getMapKeyList(scriptMap);
  }

  /**
   * 覆盖方法 刘虻 2015年3月31日
   */
  @Override
  public void unRegist(Object bean) {
    if (bean instanceof IBeanRegisterChild) {
      ((IBeanRegisterChild) bean).beforeUnRegister();
    }
    msTriggerList.remove(bean);
  }

  /**
   * 获取专供脚本使用的序列号生成器 注意：该序列号生成器只能内部使用
   * 
   * @return 序列号 2015年11月22日
   * @author 马宝刚
   */
  @Override
  public String getSn() {
    if (sn == null) {
      sn = FSN.newInstance("_SCRIPT_SN", 40);
    }
    return sn.getSID();
  }

  /**
   * 获取包资源管理服务
   * 
   * @return 包资源管理服务类实例 2019年01月18日
   * @author MBG
   */
  public synchronized ResourceService getResourceService() {
    if (rs == null) {
      rs = new ResourceService();
      rs.setBase(this);
    }
    return rs;
  }

  /**
   * 清除指定脚本的运行错误信息
   * 
   * @param id 脚本的运行错误信息 2019年1月28日
   * @author MBG
   */
  @Override
  public void clearScriptRunError(String id) {
    // 获取指定的脚本信息类
    clearScriptRunError(getScriptInfo(id, false));
  }

  /**
   * 清除指定脚本的运行错误信息
   * 
   * @param sVO 脚本信息类 2019年1月28日
   * @author MBG
   */
  public void clearScriptRunError(ScriptVO sVO) {
    if (sVO == null) {
      return;
    }
    sVO.runErrorCount = 0;
    sVO.runErrorTime = "";
    sVO.runErrorMsg = "";
    sVO.hasNewRunError = false;
  }

  /**
   * 获取远程脚本加载器
   * 
   * @return 远程脚本加载器 2020年7月14日
   * @author MBG
   */
  public RemoteScriptLoader getRemoteScriptLoader() {
    return rsl;
  }

  /**
   * 设置指定脚本运行时错误信息 注意：不能直接设置到内置到脚本中的ScriptVO中 ，因为设置到脚本中的ScriptVO是克隆后的，防止人为被修改
   * 
   * @param id       脚本主键
   * @param ts       发生错误时的时间戳
   * @param errorMsg 错误信息 2019年1月28日
   * @author MBG
   */
  @Override
  public void setScriptRunError(String id, String ts, String errorMsg) {
    // 获取指定的脚本信息类
    ScriptVO sVO = getScriptInfo(id, false);
    if (sVO == null) {
      return;
    }
    sVO.runErrorCount++;
    sVO.runErrorTime = ts;
    sVO.runErrorMsg = errorMsg;
    sVO.hasNewRunError = true;
  }
  
  /**
   * 设置编译后的脚本类全路径
   * @param binPath 编译后的脚本类全路径
   * 2021年9月11日
   * @author MBG
   */
  public void setScriptClassPath(String binPath) {
	  this.binPath = binPath;
  }
  
  /**
   * 设置脚本源文件根路径
   * @param srcPath 脚本源文件根路径
   * 2021年9月11日
   * @author MBG
   */
  public void setSourceBasePath(String srcPath) {
    this.srcPath = srcPath;
  }
}
